Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #5670 from ynput/enhancement/OP-6890_unreal-improv…
Browse files Browse the repository at this point in the history
…ed_update

Unreal: Changed behaviour for updating assets
  • Loading branch information
simonebarbieri authored Oct 26, 2023
2 parents 9ed1e77 + 2f48446 commit 4a34bfe
Show file tree
Hide file tree
Showing 11 changed files with 723 additions and 342 deletions.
140 changes: 140 additions & 0 deletions openpype/hosts/unreal/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
from openpype.pipeline import (
register_loader_plugin_path,
register_creator_plugin_path,
register_inventory_action_path,
deregister_loader_plugin_path,
deregister_creator_plugin_path,
deregister_inventory_action_path,
AYON_CONTAINER_ID,
legacy_io,
)
Expand All @@ -28,6 +30,7 @@
logger = logging.getLogger("openpype.hosts.unreal")

AYON_CONTAINERS = "AyonContainers"
AYON_ASSET_DIR = "/Game/Ayon/Assets"
CONTEXT_CONTAINER = "Ayon/context.json"
UNREAL_VERSION = semver.VersionInfo(
*os.getenv("AYON_UNREAL_VERSION").split(".")
Expand Down Expand Up @@ -127,6 +130,7 @@ def install():
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
register_loader_plugin_path(str(LOAD_PATH))
register_creator_plugin_path(str(CREATE_PATH))
register_inventory_action_path(str(INVENTORY_PATH))
_register_callbacks()
_register_events()

Expand All @@ -136,6 +140,7 @@ def uninstall():
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
deregister_loader_plugin_path(str(LOAD_PATH))
deregister_creator_plugin_path(str(CREATE_PATH))
deregister_inventory_action_path(str(INVENTORY_PATH))


def _register_callbacks():
Expand Down Expand Up @@ -649,6 +654,141 @@ def generate_sequence(h, h_dir):
return sequence, (min_frame, max_frame)


def _get_comps_and_assets(
component_class, asset_class, old_assets, new_assets, selected
):
eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)

components = []
if selected:
sel_actors = eas.get_selected_level_actors()
for actor in sel_actors:
comps = actor.get_components_by_class(component_class)
components.extend(comps)
else:
comps = eas.get_all_level_actors_components()
components = [
c for c in comps if isinstance(c, component_class)
]

# Get all the static meshes among the old assets in a dictionary with
# the name as key
selected_old_assets = {}
for a in old_assets:
asset = unreal.EditorAssetLibrary.load_asset(a)
if isinstance(asset, asset_class):
selected_old_assets[asset.get_name()] = asset

# Get all the static meshes among the new assets in a dictionary with
# the name as key
selected_new_assets = {}
for a in new_assets:
asset = unreal.EditorAssetLibrary.load_asset(a)
if isinstance(asset, asset_class):
selected_new_assets[asset.get_name()] = asset

return components, selected_old_assets, selected_new_assets


def replace_static_mesh_actors(old_assets, new_assets, selected):
smes = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem)

static_mesh_comps, old_meshes, new_meshes = _get_comps_and_assets(
unreal.StaticMeshComponent,
unreal.StaticMesh,
old_assets,
new_assets,
selected
)

for old_name, old_mesh in old_meshes.items():
new_mesh = new_meshes.get(old_name)

if not new_mesh:
continue

smes.replace_mesh_components_meshes(
static_mesh_comps, old_mesh, new_mesh)


def replace_skeletal_mesh_actors(old_assets, new_assets, selected):
skeletal_mesh_comps, old_meshes, new_meshes = _get_comps_and_assets(
unreal.SkeletalMeshComponent,
unreal.SkeletalMesh,
old_assets,
new_assets,
selected
)

for old_name, old_mesh in old_meshes.items():
new_mesh = new_meshes.get(old_name)

if not new_mesh:
continue

for comp in skeletal_mesh_comps:
if comp.get_skeletal_mesh_asset() == old_mesh:
comp.set_skeletal_mesh_asset(new_mesh)


def replace_geometry_cache_actors(old_assets, new_assets, selected):
geometry_cache_comps, old_caches, new_caches = _get_comps_and_assets(
unreal.GeometryCacheComponent,
unreal.GeometryCache,
old_assets,
new_assets,
selected
)

for old_name, old_mesh in old_caches.items():
new_mesh = new_caches.get(old_name)

if not new_mesh:
continue

for comp in geometry_cache_comps:
if comp.get_editor_property("geometry_cache") == old_mesh:
comp.set_geometry_cache(new_mesh)


def delete_asset_if_unused(container, asset_content):
ar = unreal.AssetRegistryHelpers.get_asset_registry()

references = set()

for asset_path in asset_content:
asset = ar.get_asset_by_object_path(asset_path)
refs = ar.get_referencers(
asset.package_name,
unreal.AssetRegistryDependencyOptions(
include_soft_package_references=False,
include_hard_package_references=True,
include_searchable_names=False,
include_soft_management_references=False,
include_hard_management_references=False
))
if not refs:
continue
references = references.union(set(refs))

# Filter out references that are in the Temp folder
cleaned_references = {
ref for ref in references if not str(ref).startswith("/Temp/")}

# Check which of the references are Levels
for ref in cleaned_references:
loaded_asset = unreal.EditorAssetLibrary.load_asset(ref)
if isinstance(loaded_asset, unreal.World):
# If there is at least a level, we can stop, we don't want to
# delete the container
return

unreal.log("Previous version unused, deleting...")

# No levels, delete the asset
unreal.EditorAssetLibrary.delete_directory(container["namespace"])


@contextmanager
def maintained_selection():
"""Stub to be either implemented or replaced.
Expand Down
66 changes: 66 additions & 0 deletions openpype/hosts/unreal/plugins/inventory/delete_unused_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import unreal

from openpype.hosts.unreal.api.tools_ui import qt_app_context
from openpype.hosts.unreal.api.pipeline import delete_asset_if_unused
from openpype.pipeline import InventoryAction


class DeleteUnusedAssets(InventoryAction):
"""Delete all the assets that are not used in any level.
"""

label = "Delete Unused Assets"
icon = "trash"
color = "red"
order = 1

dialog = None

def _delete_unused_assets(self, containers):
allowed_families = ["model", "rig"]

for container in containers:
container_dir = container.get("namespace")
if container.get("family") not in allowed_families:
unreal.log_warning(
f"Container {container_dir} is not supported.")
continue

asset_content = unreal.EditorAssetLibrary.list_assets(
container_dir, recursive=True, include_folder=False
)

delete_asset_if_unused(container, asset_content)

def _show_confirmation_dialog(self, containers):
from qtpy import QtCore
from openpype.widgets import popup
from openpype.style import load_stylesheet

dialog = popup.Popup()
dialog.setWindowFlags(
QtCore.Qt.Window
| QtCore.Qt.WindowStaysOnTopHint
)
dialog.setFocusPolicy(QtCore.Qt.StrongFocus)
dialog.setWindowTitle("Delete all unused assets")
dialog.setMessage(
"You are about to delete all the assets in the project that \n"
"are not used in any level. Are you sure you want to continue?"
)
dialog.setButtonText("Delete")

dialog.on_clicked.connect(
lambda: self._delete_unused_assets(containers)
)

dialog.show()
dialog.raise_()
dialog.activateWindow()
dialog.setStyleSheet(load_stylesheet())

self.dialog = dialog

def process(self, containers):
with qt_app_context():
self._show_confirmation_dialog(containers)
84 changes: 84 additions & 0 deletions openpype/hosts/unreal/plugins/inventory/update_actors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import unreal

from openpype.hosts.unreal.api.pipeline import (
ls,
replace_static_mesh_actors,
replace_skeletal_mesh_actors,
replace_geometry_cache_actors,
)
from openpype.pipeline import InventoryAction


def update_assets(containers, selected):
allowed_families = ["model", "rig"]

# Get all the containers in the Unreal Project
all_containers = ls()

for container in containers:
container_dir = container.get("namespace")
if container.get("family") not in allowed_families:
unreal.log_warning(
f"Container {container_dir} is not supported.")
continue

# Get all containers with same asset_name but different objectName.
# These are the containers that need to be updated in the level.
sa_containers = [
i
for i in all_containers
if (
i.get("asset_name") == container.get("asset_name") and
i.get("objectName") != container.get("objectName")
)
]

asset_content = unreal.EditorAssetLibrary.list_assets(
container_dir, recursive=True, include_folder=False
)

# Update all actors in level
for sa_cont in sa_containers:
sa_dir = sa_cont.get("namespace")
old_content = unreal.EditorAssetLibrary.list_assets(
sa_dir, recursive=True, include_folder=False
)

if container.get("family") == "rig":
replace_skeletal_mesh_actors(
old_content, asset_content, selected)
replace_static_mesh_actors(
old_content, asset_content, selected)
elif container.get("family") == "model":
if container.get("loader") == "PointCacheAlembicLoader":
replace_geometry_cache_actors(
old_content, asset_content, selected)
else:
replace_static_mesh_actors(
old_content, asset_content, selected)

unreal.EditorLevelLibrary.save_current_level()


class UpdateAllActors(InventoryAction):
"""Update all the Actors in the current level to the version of the asset
selected in the scene manager.
"""

label = "Replace all Actors in level to this version"
icon = "arrow-up"

def process(self, containers):
update_assets(containers, False)


class UpdateSelectedActors(InventoryAction):
"""Update only the selected Actors in the current level to the version
of the asset selected in the scene manager.
"""

label = "Replace selected Actors in level to this version"
icon = "arrow-up"

def process(self, containers):
update_assets(containers, True)
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def load(self, context, name, namespace, data):
"""

# Create directory for asset and ayon container
root = "/Game/Ayon/Assets"
root = unreal_pipeline.AYON_ASSET_DIR
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
Expand Down
Loading

0 comments on commit 4a34bfe

Please sign in to comment.