Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Build shot structure from the database #4

Merged
merged 14 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
277 changes: 277 additions & 0 deletions client/ayon_unreal/api/hierarchy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
from pathlib import Path
from qtpy import QtWidgets, QtCore, QtGui

from ayon_api import get_folders_hierarchy
from ayon_core import (
resources,
style
)
from ayon_core.pipeline import get_current_project_name
from ayon_core.tools.utils import (
show_message_dialog,
PlaceholderLineEdit,
SquareButton,
)
from ayon_core.tools.utils import SimpleFoldersWidget

from ayon_unreal.api.pipeline import (
generate_sequence,
set_sequence_hierarchy,
)

import unreal


class ConfirmButton(SquareButton):
def __init__(self, parent=None):
super(ConfirmButton, self).__init__(parent)
self.setText("Confirm")


class FolderSelector(QtWidgets.QWidget):
"""Widget for selecting a folder from the project hierarchy."""

confirm_btn = None

def __init__(self, controller=None, parent=None, project=None):
if not project:
raise ValueError("Project name not provided.")

super(FolderSelector, self).__init__(parent)

icon = QtGui.QIcon(resources.get_ayon_icon_filepath())
self.setWindowIcon(icon)
self.setWindowTitle("Folder Selector")
self.setFocusPolicy(QtCore.Qt.StrongFocus)
self.setAttribute(QtCore.Qt.WA_DeleteOnClose, False)

self.setStyleSheet(style.load_stylesheet())

# Allow minimize
self.setWindowFlags(
QtCore.Qt.Window
| QtCore.Qt.CustomizeWindowHint
| QtCore.Qt.WindowTitleHint
| QtCore.Qt.WindowMinimizeButtonHint
| QtCore.Qt.WindowCloseButtonHint
)

content_body = QtWidgets.QWidget(self)

# Folders
folders_wrapper = QtWidgets.QWidget(content_body)

folders_filter_text = PlaceholderLineEdit(folders_wrapper)
folders_filter_text.setPlaceholderText("Filter folders...")

folders_widget = SimpleFoldersWidget(
controller=None, parent=folders_wrapper)
folders_widget.set_project_name(project_name=project)

folders_wrapper_layout = QtWidgets.QVBoxLayout(folders_wrapper)
folders_wrapper_layout.setContentsMargins(0, 0, 0, 0)
folders_wrapper_layout.addWidget(folders_filter_text, 0)
folders_wrapper_layout.addWidget(folders_widget, 1)

# Footer
footer_widget = QtWidgets.QWidget(content_body)

self.confirm_btn = ConfirmButton(footer_widget)

footer_layout = QtWidgets.QHBoxLayout(footer_widget)
footer_layout.setContentsMargins(0, 0, 0, 0)
footer_layout.addWidget(self.confirm_btn, 0)

# Main layout
content_layout = QtWidgets.QVBoxLayout(content_body)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.addWidget(folders_wrapper, 1)
content_layout.addWidget(footer_widget, 0)

layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(content_body, 1)

folders_filter_text.textChanged.connect(
self._on_filter_text_changed)

self._controller = controller

self._confirm_btn = self.confirm_btn
self._folders_widget = folders_widget

self.resize(300, 400)

self.show()
self.raise_()
self.activateWindow()

def _on_filter_text_changed(self, text):
self._folders_widget.set_name_filter(text)

def get_selected_folder(self):
return self._folders_widget.get_selected_folder_path()


def _create_level(path, name, master_level):
# Create the level
level_path = f"{path}/{name}_map"
level_package = f"{level_path}.{name}_map"
unreal.EditorLevelLibrary.new_level(level_path)

# Add the level to the master level as sublevel
unreal.EditorLevelLibrary.load_level(master_level)
unreal.EditorLevelUtils.add_level_to_world(
unreal.EditorLevelLibrary.get_editor_world(),
level_package,
unreal.LevelStreamingDynamic
)
unreal.EditorLevelLibrary.save_all_dirty_levels()

return level_package


def _create_sequence(
element, sequence_path, master_level,
parent_path="", parents_sequence=[], parents_frame_range=[]
):
"""
Create sequences from the hierarchy element.

Args:
element (dict): The hierarchy element.
sequence_path (str): The sequence path.
master_level (str): The master level package.
parent_path (str): The parent path.
parents_sequence (list): The list of parent sequences.
parents_frame_range (list): The list of parent frame ranges.
"""
name = element["name"]
path = f"{parent_path}/{name}"
hierarchy_dir = f"{sequence_path}{path}"
children = element["children"]

# Create sequence for the current element
sequence, frame_range = generate_sequence(name, hierarchy_dir)

sequences = parents_sequence.copy() + [sequence]
frame_ranges = parents_frame_range.copy() + [frame_range]

if children:
# Traverse the children and create sequences recursively
for child in children:
_create_sequence(
child, sequence_path, master_level, parent_path=path,
parents_sequence=sequences, parents_frame_range=frame_ranges)
else:
level = _create_level(hierarchy_dir, name, master_level)

# Create the sequence hierarchy. Add each child to its parent
for i in range(len(parents_sequence) - 1):
set_sequence_hierarchy(
parents_sequence[i], parents_sequence[i + 1],
parents_frame_range[i][1],
parents_frame_range[i + 1][0], parents_frame_range[i + 1][1],
[level])

# Add the newly created sequence to its parent
set_sequence_hierarchy(
parents_sequence[-1], sequence,
parents_frame_range[-1][1],
frame_range[0], frame_range[1],
[level])


def _find_in_hierarchy(hierarchy, path):
"""
Find the hierarchy element from the path.

Args:
hierarchy (list): The hierarchy list.
path (str): The path to find.
"""
elements = path.split("/")
current_element = elements[0]

for element in hierarchy:
if element["name"] == current_element:
if len(elements) == 1:
return element

remaining_path = "/".join(elements[1:])
return _find_in_hierarchy(element["children"], remaining_path)

return None


def _on_confirm_clicked(folder_selector, sequence_path, project):
selected_root = folder_selector.get_selected_folder()
sequence_root_name = selected_root.lstrip("/")
project_name = get_current_project_name()
sequence_root = f"{sequence_path}/{project_name}/{sequence_root_name}"
asset_content = unreal.EditorAssetLibrary.list_assets(
sequence_root, recursive=False, include_folder=True)

if asset_content:
msg = (
"The sequence folder is not empty. Please delete the contents "
"before building the sequence hierarchy.")
show_message_dialog(
parent=None,
title="Sequence Folder not empty",
message=msg,
level="critical")

return

hierarchy = get_folders_hierarchy(project_name=project)["hierarchy"]

# Find the sequence root element in the hierarchy
hierarchy_element = _find_in_hierarchy(hierarchy, sequence_root_name)

# Raise an error if the sequence root element is not found
if not hierarchy_element:
raise ValueError(f"Could not find {sequence_root_name} in hierarchy")

# Create the master level
master_level_name = sequence_root_name.split("/")[-1]
master_level_path = f"{sequence_root}/{master_level_name}_map"
master_level_package = f"{master_level_path}.{master_level_name}_map"
unreal.EditorLevelLibrary.new_level(master_level_path)

# Start creating sequences from the root element
_create_sequence(
hierarchy_element, Path(sequence_root).parent.as_posix(),
master_level_package)

# List all the assets in the sequence path and save them
asset_content = unreal.EditorAssetLibrary.list_assets(
sequence_root, recursive=True, include_folder=False
)

for a in asset_content:
unreal.EditorAssetLibrary.save_asset(a)

# Load the master level
unreal.EditorLevelLibrary.load_level(master_level_package)

folder_selector.close()


def build_sequence_hierarchy():
"""
Builds the sequence hierarchy by creating sequences from the root element.

Raises:
ValueError: If the sequence root element is not found in the hierarchy.
"""
print("Building sequence hierarchy...")

project = get_current_project_name()

sequence_path = "/Game/Ayon/"

folder_selector = FolderSelector(project=project)

folder_selector.confirm_btn.clicked.connect(
lambda: _on_confirm_clicked(folder_selector, sequence_path, project)
)
1 change: 1 addition & 0 deletions client/ayon_unreal/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,7 @@ def generate_sequence(h, h_dir):
parent_ids=[e["id"]],
fields={"id", "attrib.clipIn", "attrib.clipOut"}
))
fps = folder_entity["attrib"].get("fps")
moonyuet marked this conversation as resolved.
Show resolved Hide resolved

fps = folder_entity["attrib"].get("fps")
min_frame = min(start_frames, default=sequence.get_playback_start())
Expand Down
10 changes: 9 additions & 1 deletion client/ayon_unreal/api/tools_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ayon_core.tools.utils import host_tools
from ayon_core.tools.utils.lib import qt_app_context
from ayon_unreal.api import rendering
from ayon_unreal.api import hierarchy


class ToolsBtnsWidget(QtWidgets.QWidget):
Expand All @@ -21,6 +22,8 @@ def __init__(self, parent=None):
publish_btn = QtWidgets.QPushButton("Publisher...", self)
manage_btn = QtWidgets.QPushButton("Manage...", self)
render_btn = QtWidgets.QPushButton("Render...", self)
sequence_btn = QtWidgets.QPushButton(
"Build sequence hierarchy...", self)
experimental_tools_btn = QtWidgets.QPushButton(
"Experimental tools...", self
)
Expand All @@ -31,13 +34,15 @@ def __init__(self, parent=None):
layout.addWidget(publish_btn, 0)
layout.addWidget(manage_btn, 0)
layout.addWidget(render_btn, 0)
layout.addWidget(sequence_btn, 0)
layout.addWidget(experimental_tools_btn, 0)
layout.addStretch(1)

load_btn.clicked.connect(self._on_load)
publish_btn.clicked.connect(self._on_publish)
manage_btn.clicked.connect(self._on_manage)
render_btn.clicked.connect(self._on_render)
sequence_btn.clicked.connect(self._on_sequence)
experimental_tools_btn.clicked.connect(self._on_experimental)

def _on_create(self):
Expand All @@ -55,6 +60,9 @@ def _on_manage(self):
def _on_render(self):
rendering.start_rendering()

def _on_sequence(self):
hierarchy.build_sequence_hierarchy()

def _on_experimental(self):
self.tool_required.emit("experimental_tools")

Expand Down Expand Up @@ -125,7 +133,7 @@ class WindowCache:

@classmethod
def _before_show(cls):
"""Create QApplication if does not exist yet."""
"""Create QApplication if does not exists yet."""
if not cls._first_show:
return

Expand Down