Merge branch 'develop' of https://github.com/ynput/ayon-core into chore/houdini_remove_unused_create_remote_publish

This commit is contained in:
Roy Nieterau 2024-06-25 11:44:21 +02:00
commit ea647c060b
9 changed files with 157 additions and 111 deletions

View file

@ -2,6 +2,8 @@
from .cache import CacheItem, NestedCacheItem
from .projects import (
StatusItem,
StatusStates,
ProjectItem,
ProjectsModel,
PROJECTS_MODEL_SENDER,
@ -21,6 +23,8 @@ __all__ = (
"CacheItem",
"NestedCacheItem",
"StatusItem",
"StatusStates",
"ProjectItem",
"ProjectsModel",
"PROJECTS_MODEL_SENDER",

View file

@ -1,8 +1,8 @@
import contextlib
from abc import ABCMeta, abstractmethod
from abc import ABC, abstractmethod
from typing import Dict, Any
import ayon_api
import six
from ayon_core.style import get_default_entity_icon_color
from ayon_core.lib import CacheItem, NestedCacheItem
@ -10,8 +10,14 @@ from ayon_core.lib import CacheItem, NestedCacheItem
PROJECTS_MODEL_SENDER = "projects.model"
@six.add_metaclass(ABCMeta)
class AbstractHierarchyController:
class StatusStates:
not_started = "not_started"
in_progress = "in_progress"
done = "done"
blocked = "blocked"
class AbstractHierarchyController(ABC):
@abstractmethod
def emit_event(self, topic, data, source):
pass
@ -25,18 +31,24 @@ class StatusItem:
color (str): Status color in hex ("#434a56").
short (str): Short status name ("NRD").
icon (str): Icon name in MaterialIcons ("fiber_new").
state (Literal["not_started", "in_progress", "done", "blocked"]):
Status state.
state (str): Status state.
"""
def __init__(self, name, color, short, icon, state):
self.name = name
self.color = color
self.short = short
self.icon = icon
self.state = state
def __init__(
self,
name: str,
color: str,
short: str,
icon: str,
state: str
):
self.name: str = name
self.color: str = color
self.short: str = short
self.icon: str = icon
self.state: str = state
def to_data(self):
def to_data(self) -> Dict[str, Any]:
return {
"name": self.name,
"color": self.color,

View file

@ -217,7 +217,9 @@ class InventoryModel(QtGui.QStandardItemModel):
version_label = format_version(version_item.version)
is_hero = version_item.version < 0
is_latest = version_item.is_latest
if not is_latest:
# TODO maybe use different colors for last approved and last
# version? Or don't care about color at all?
if not is_latest and not version_item.is_last_approved:
version_color = self.OUTDATED_COLOR
status_name = version_item.status

View file

@ -3,7 +3,9 @@ import collections
import ayon_api
from ayon_api.graphql import GraphQlQuery
from ayon_core.host import ILoadHost
from ayon_core.tools.common_models.projects import StatusStates
# --- Implementation that should be in ayon-python-api ---
@ -149,26 +151,35 @@ class RepresentationInfo:
class VersionItem:
def __init__(self, version_id, product_id, version, status, is_latest):
self.version = version
self.version_id = version_id
self.product_id = product_id
self.version = version
self.status = status
self.is_latest = is_latest
def __init__(
self,
version_id: str,
product_id: str,
version: int,
status: str,
is_latest: bool,
is_last_approved: bool,
):
self.version_id: str = version_id
self.product_id: str = product_id
self.version: int = version
self.status: str = status
self.is_latest: bool = is_latest
self.is_last_approved: bool = is_last_approved
@property
def is_hero(self):
return self.version < 0
@classmethod
def from_entity(cls, version_entity, is_latest):
def from_entity(cls, version_entity, is_latest, is_last_approved):
return cls(
version_id=version_entity["id"],
product_id=version_entity["productId"],
version=version_entity["version"],
status=version_entity["status"],
is_latest=is_latest,
is_last_approved=is_last_approved,
)
@ -275,6 +286,11 @@ class ContainersModel:
if product_id not in self._version_items_by_product_id
}
if missing_ids:
status_items_by_name = {
status_item.name: status_item
for status_item in self._controller.get_project_status_items()
}
def version_sorted(entity):
return entity["version"]
@ -300,9 +316,21 @@ class ContainersModel:
version_entities_by_product_id.items()
):
last_version = abs(version_entities[-1]["version"])
last_approved_id = None
for version_entity in version_entities:
status_item = status_items_by_name.get(
version_entity["status"]
)
if status_item is None:
continue
if status_item.state == StatusStates.done:
last_approved_id = version_entity["id"]
version_items_by_id = {
entity["id"]: VersionItem.from_entity(
entity, abs(entity["version"]) == last_version
entity,
abs(entity["version"]) == last_version,
entity["id"] == last_approved_id
)
for entity in version_entities
}

View file

@ -233,19 +233,38 @@ class SceneInventoryView(QtWidgets.QTreeView):
has_outdated = False
has_loaded_hero_versions = False
has_available_hero_version = False
for version_items_by_id in version_items_by_product_id.values():
has_outdated_approved = False
last_version_by_product_id = {}
for product_id, version_items_by_id in (
version_items_by_product_id.items()
):
_has_outdated_approved = False
_last_approved_version_item = None
for version_item in version_items_by_id.values():
if version_item.is_hero:
has_available_hero_version = True
elif version_item.is_last_approved:
_last_approved_version_item = version_item
_has_outdated_approved = True
if version_item.version_id not in version_ids:
continue
if version_item.is_hero:
has_loaded_hero_versions = True
elif not version_item.is_latest:
has_outdated = True
if (
_has_outdated_approved
and _last_approved_version_item is not None
):
last_version_by_product_id[product_id] = (
_last_approved_version_item
)
has_outdated_approved = True
switch_to_versioned = None
if has_loaded_hero_versions:
update_icon = qtawesome.icon(
@ -261,6 +280,42 @@ class SceneInventoryView(QtWidgets.QTreeView):
lambda: self._on_switch_to_versioned(item_ids)
)
update_to_last_approved_action = None
approved_version_by_item_id = {}
if has_outdated_approved:
for container_item in container_items_by_id.values():
repre_id = container_item.representation_id
repre_info = repre_info_by_id.get(repre_id)
if not repre_info or not repre_info.is_valid:
continue
version_item = last_version_by_product_id.get(
repre_info.product_id
)
if (
version_item is None
or version_item.version_id == repre_info.version_id
):
continue
approved_version_by_item_id[container_item.item_id] = (
version_item.version
)
if approved_version_by_item_id:
update_icon = qtawesome.icon(
"fa.angle-double-up",
color="#00f0b4"
)
update_to_last_approved_action = QtWidgets.QAction(
update_icon,
"Update to last approved",
menu
)
update_to_last_approved_action.triggered.connect(
lambda: self._update_containers_to_approved_versions(
approved_version_by_item_id
)
)
update_to_latest_action = None
if has_outdated or has_loaded_hero_versions:
update_icon = qtawesome.icon(
@ -299,7 +354,9 @@ class SceneInventoryView(QtWidgets.QTreeView):
# set version
set_version_action = None
if active_repre_id is not None:
set_version_icon = qtawesome.icon("fa.hashtag", color=DEFAULT_COLOR)
set_version_icon = qtawesome.icon(
"fa.hashtag", color=DEFAULT_COLOR
)
set_version_action = QtWidgets.QAction(
set_version_icon,
"Set version",
@ -323,6 +380,9 @@ class SceneInventoryView(QtWidgets.QTreeView):
if switch_to_versioned:
menu.addAction(switch_to_versioned)
if update_to_last_approved_action:
menu.addAction(update_to_last_approved_action)
if update_to_latest_action:
menu.addAction(update_to_latest_action)
@ -970,3 +1030,24 @@ class SceneInventoryView(QtWidgets.QTreeView):
"""
versions = [version for _ in range(len(item_ids))]
self._update_containers(item_ids, versions)
def _update_containers_to_approved_versions(
self, approved_version_by_item_id
):
"""Helper to update items to given version (or version per item)
If at least one item is specified this will always try to refresh
the inventory even if errors occurred on any of the items.
Arguments:
approved_version_by_item_id (Dict[str, int]): Version to set by
item id.
"""
versions = []
item_ids = []
for item_id, version in approved_version_by_item_id.items():
item_ids.append(item_id)
versions.append(version)
self._update_containers(item_ids, versions)

View file

@ -4,10 +4,6 @@ from .pipeline import (
containerise
)
from .plugin import (
Creator,
)
from .lib import (
lsattr,
lsattrs,
@ -23,8 +19,6 @@ __all__ = [
"ls",
"containerise",
"Creator",
# Utility functions
"lsattr",
"lsattrs",

View file

@ -10,8 +10,7 @@ import hou
import pyblish.api
from ayon_core.pipeline import (
CreatorError,
LegacyCreator,
Creator as NewCreator,
Creator,
CreatedInstance,
AYON_INSTANCE_ID,
AVALON_INSTANCE_ID,
@ -26,80 +25,6 @@ from .lib import imprint, read, lsattr, add_self_publish_button
SETTINGS_CATEGORY = "houdini"
class Creator(LegacyCreator):
"""Creator plugin to create instances in Houdini
To support the wide range of node types for render output (Alembic, VDB,
Mantra) the Creator needs a node type to create the correct instance
By default, if none is given, is `geometry`. An example of accepted node
types: geometry, alembic, ifd (mantra)
Please check the Houdini documentation for more node types.
Tip: to find the exact node type to create press the `i` left of the node
when hovering over a node. The information is visible under the name of
the node.
Deprecated:
This creator is deprecated and will be removed in future version.
"""
defaults = ['Main']
def __init__(self, *args, **kwargs):
super(Creator, self).__init__(*args, **kwargs)
self.nodes = []
def process(self):
"""This is the base functionality to create instances in Houdini
The selected nodes are stored in self to be used in an override method.
This is currently necessary in order to support the multiple output
types in Houdini which can only be rendered through their own node.
Default node type if none is given is `geometry`
It also makes it easier to apply custom settings per instance type
Example of override method for Alembic:
def process(self):
instance = super(CreateEpicNode, self, process()
# Set parameters for Alembic node
instance.setParms(
{"sop_path": "$HIP/%s.abc" % self.nodes[0]}
)
Returns:
hou.Node
"""
try:
if (self.options or {}).get("useSelection"):
self.nodes = hou.selectedNodes()
# Get the node type and remove it from the data, not needed
node_type = self.data.pop("node_type", None)
if node_type is None:
node_type = "geometry"
# Get out node
out = hou.node("/out")
instance = out.createNode(node_type, node_name=self.name)
instance.moveToGoodPosition()
imprint(instance, self.data)
self._process(instance)
except hou.Error as er:
six.reraise(
CreatorError,
CreatorError("Creator error: {}".format(er)),
sys.exc_info()[2])
class HoudiniCreatorBase(object):
@staticmethod
def cache_instance_data(shared_data):
@ -175,7 +100,7 @@ class HoudiniCreatorBase(object):
@six.add_metaclass(ABCMeta)
class HoudiniCreator(NewCreator, HoudiniCreatorBase):
class HoudiniCreator(Creator, HoudiniCreatorBase):
"""Base class for most of the Houdini creator plugins."""
selected_nodes = []
settings_name = None

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'houdini' version."""
__version__ = "0.3.4"
__version__ = "0.3.5"

View file

@ -1,6 +1,6 @@
name = "houdini"
title = "Houdini"
version = "0.3.4"
version = "0.3.5"
client_dir = "ayon_houdini"