Merge branch 'develop' into enhancement/save_as_published_workfiles

This commit is contained in:
Jakub Trllo 2022-03-30 16:03:39 +02:00
commit b212899cc4
154 changed files with 2139 additions and 1211 deletions

View file

@ -308,7 +308,6 @@ class ContextDialog(QtWidgets.QDialog):
self._validate_strict()
def _set_asset_to_tasks_widget(self):
# filter None docs they are silo
asset_id = self._assets_widget.get_selected_asset_id()
self._tasks_widget.set_asset_id(asset_id)

View file

@ -1,6 +1,7 @@
import os
from avalon import api
from Qt import QtWidgets, QtGui
from openpype import PLUGINS_DIR
from openpype import style
from openpype.api import Logger, resources
@ -8,7 +9,10 @@ from openpype.lib import (
ApplictionExecutableNotFound,
ApplicationLaunchFailed
)
from Qt import QtWidgets, QtGui
from openpype.pipeline import (
LauncherAction,
register_launcher_action_path,
)
def register_actions_from_paths(paths):
@ -29,14 +33,15 @@ def register_actions_from_paths(paths):
print("Path was not found: {}".format(path))
continue
api.register_plugin_path(api.Action, path)
register_launcher_action_path(path)
def register_config_actions():
"""Register actions from the configuration for Launcher"""
actions_dir = os.path.join(PLUGINS_DIR, "actions")
register_actions_from_paths([actions_dir])
if os.path.exists(actions_dir):
register_actions_from_paths([actions_dir])
def register_environment_actions():
@ -46,7 +51,9 @@ def register_environment_actions():
register_actions_from_paths(paths_str.split(os.pathsep))
class ApplicationAction(api.Action):
# TODO move to 'openpype.pipeline.actions'
# - remove Qt related stuff and implement exceptions to show error in launcher
class ApplicationAction(LauncherAction):
"""Pype's application launcher
Application action based on pype's ApplicationManager system.
@ -74,7 +81,7 @@ class ApplicationAction(api.Action):
@property
def log(self):
if self._log is None:
self._log = Logger().get_logger(self.__class__.__name__)
self._log = Logger.get_logger(self.__class__.__name__)
return self._log
def is_compatible(self, session):

View file

@ -1,19 +1,3 @@
"""Utility script for updating database with configuration files
Until assets are created entirely in the database, this script
provides a bridge between the file-based project inventory and configuration.
- Migrating an old project:
$ python -m avalon.inventory --extract --silo-parent=f02_prod
$ python -m avalon.inventory --upload
- Managing an existing project:
1. Run `python -m avalon.inventory --load`
2. Update the .inventory.toml or .config.toml
3. Run `python -m avalon.inventory --save`
"""
import os
from Qt import QtGui
import qtawesome

View file

@ -8,12 +8,13 @@ import time
import appdirs
from Qt import QtCore, QtGui
import qtawesome
from avalon import api
from openpype.lib import JSONSettingRegistry
from openpype.lib.applications import (
CUSTOM_LAUNCH_APP_GROUPS,
ApplicationManager
)
from openpype.pipeline import discover_launcher_actions
from openpype.tools.utils.lib import (
DynamicQThread,
get_project_icon,
@ -68,7 +69,7 @@ class ActionModel(QtGui.QStandardItemModel):
def discover(self):
"""Set up Actions cache. Run this for each new project."""
# Discover all registered actions
actions = api.discover(api.Action)
actions = discover_launcher_actions()
# Get available project actions and the application actions
app_actions = self.get_application_actions()

View file

@ -9,14 +9,14 @@ from openpype.tools.loader.widgets import (
ThumbnailWidget,
VersionWidget,
FamilyListView,
RepresentationWidget
RepresentationWidget,
SubsetWidget
)
from openpype.tools.utils.assets_widget import MultiSelectAssetsWidget
from openpype.modules import ModulesManager
from . import lib
from .widgets import LibrarySubsetWidget
module = sys.modules[__name__]
module.window = None
@ -92,7 +92,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
# --- Middle part ---
# Subsets widget
subsets_widget = LibrarySubsetWidget(
subsets_widget = SubsetWidget(
dbcon,
self.groups_config,
self.family_config_cache,
@ -448,10 +448,7 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
def _set_context(self, context, refresh=True):
"""Set the selection in the interface using a context.
The context must contain `asset` data by name.
Note: Prior to setting context ensure `refresh` is triggered so that
the "silos" are listed correctly, aside from that setting the
context will force a refresh further down because it changes
the active silo and asset.
Args:
context (dict): The context to apply.
Returns:
@ -463,12 +460,6 @@ class LibraryLoaderWindow(QtWidgets.QDialog):
return
if refresh:
# Workaround:
# Force a direct (non-scheduled) refresh prior to setting the
# asset widget's silo and asset selection to ensure it's correctly
# displaying the silo tabs. Calling `window.refresh()` and directly
# `window.set_context()` the `set_context()` seems to override the
# scheduled refresh and the silo tabs are not shown.
self._refresh_assets()
self._assets_widget.select_asset_by_name(asset_name)

View file

@ -1,7 +1,6 @@
import os
import importlib
import logging
from openpype.api import Anatomy
log = logging.getLogger(__name__)
@ -20,14 +19,3 @@ def find_config():
log.info("Found %s, loading.." % config)
return importlib.import_module(config)
class RegisteredRoots:
roots_per_project = {}
@classmethod
def registered_root(cls, project_name):
if project_name not in cls.roots_per_project:
cls.roots_per_project[project_name] = Anatomy(project_name).roots
return cls.roots_per_project[project_name]

View file

@ -1,18 +0,0 @@
from Qt import QtWidgets
from .lib import RegisteredRoots
from openpype.tools.loader.widgets import SubsetWidget
class LibrarySubsetWidget(SubsetWidget):
def on_copy_source(self):
"""Copy formatted source path to clipboard"""
source = self.data.get("source", None)
if not source:
return
project_name = self.dbcon.Session["AVALON_PROJECT"]
root = RegisteredRoots.registered_root(project_name)
path = source.format(root=root)
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(path)

View file

@ -290,7 +290,6 @@ class LoaderWindow(QtWidgets.QDialog):
subsets_model.clear()
self.clear_assets_underlines()
# filter None docs they are silo
asset_ids = self._assets_widget.get_selected_asset_ids()
# Start loading
subsets_widget.set_loading_state(
@ -381,17 +380,9 @@ class LoaderWindow(QtWidgets.QDialog):
The context must contain `asset` data by name.
Note: Prior to setting context ensure `refresh` is triggered so that
the "silos" are listed correctly, aside from that setting the
context will force a refresh further down because it changes
the active silo and asset.
Args:
context (dict): The context to apply.
Returns:
None
refrest (bool): Trigger refresh on context set.
"""
asset = context.get("asset", None)
@ -399,12 +390,6 @@ class LoaderWindow(QtWidgets.QDialog):
return
if refresh:
# Workaround:
# Force a direct (non-scheduled) refresh prior to setting the
# asset widget's silo and asset selection to ensure it's correctly
# displaying the silo tabs. Calling `window.refresh()` and directly
# `window.set_context()` the `set_context()` seems to override the
# scheduled refresh and the silo tabs are not shown.
self._refresh()
self._assets_widget.select_asset_by_name(asset)

View file

@ -7,9 +7,9 @@ import collections
from Qt import QtWidgets, QtCore, QtGui
from avalon import api, pipeline
from openpype.api import Anatomy
from openpype.pipeline import HeroVersionType
from openpype.pipeline.thumbnail import get_thumbnail_binary
from openpype.pipeline.load import (
discover_loader_plugins,
SubsetLoaderPlugin,
@ -640,6 +640,7 @@ class VersionTextEdit(QtWidgets.QTextEdit):
"source": None,
"raw": None
}
self._anatomy = None
# Reset
self.set_version(None)
@ -730,20 +731,20 @@ class VersionTextEdit(QtWidgets.QTextEdit):
# Add additional actions when any text so we can assume
# the version is set.
if self.toPlainText().strip():
menu.addSeparator()
action = QtWidgets.QAction("Copy source path to clipboard",
menu)
action = QtWidgets.QAction(
"Copy source path to clipboard", menu
)
action.triggered.connect(self.on_copy_source)
menu.addAction(action)
action = QtWidgets.QAction("Copy raw data to clipboard",
menu)
action = QtWidgets.QAction(
"Copy raw data to clipboard", menu
)
action.triggered.connect(self.on_copy_raw)
menu.addAction(action)
menu.exec_(event.globalPos())
del menu
def on_copy_source(self):
"""Copy formatted source path to clipboard"""
@ -751,7 +752,11 @@ class VersionTextEdit(QtWidgets.QTextEdit):
if not source:
return
path = source.format(root=api.registered_root())
project_name = self.dbcon.Session["AVALON_PROJECT"]
if self._anatomy is None or self._anatomy.project_name != project_name:
self._anatomy = Anatomy(project_name)
path = source.format(root=self._anatomy.roots)
clipboard = QtWidgets.QApplication.clipboard()
clipboard.setText(path)
@ -771,7 +776,6 @@ class VersionTextEdit(QtWidgets.QTextEdit):
class ThumbnailWidget(QtWidgets.QLabel):
aspect_ratio = (16, 9)
max_width = 300
@ -863,7 +867,7 @@ class ThumbnailWidget(QtWidgets.QLabel):
if not thumbnail_ent:
return
thumbnail_bin = pipeline.get_thumbnail_binary(
thumbnail_bin = get_thumbnail_binary(
thumbnail_ent, "thumbnail", self.dbcon
)
if not thumbnail_bin:

View file

@ -2,6 +2,7 @@ from collections import defaultdict
import logging
import os
from bson.objectid import ObjectId
import maya.cmds as cmds
from avalon import io, api
@ -157,7 +158,7 @@ def create_items_from_nodes(nodes):
return asset_view_items
for _id, id_nodes in id_hashes.items():
asset = io.find_one({"_id": io.ObjectId(_id)},
asset = io.find_one({"_id": ObjectId(_id)},
projection={"name": True})
# Skip if asset id is not found

View file

@ -6,6 +6,7 @@ import logging
import json
import six
from bson.objectid import ObjectId
import alembic.Abc
from maya import cmds
@ -231,7 +232,7 @@ def get_latest_version(asset_id, subset):
"""
subset = io.find_one({"name": subset,
"parent": io.ObjectId(asset_id),
"parent": ObjectId(asset_id),
"type": "subset"})
if not subset:
raise RuntimeError("Subset does not exist: %s" % subset)

View file

@ -5,6 +5,7 @@ from collections import defaultdict
from Qt import QtCore, QtGui
import qtawesome
from bson.objectid import ObjectId
from avalon import api, io, schema
from openpype.pipeline import HeroVersionType
@ -299,7 +300,7 @@ class InventoryModel(TreeModel):
for repre_id, group_dict in sorted(grouped.items()):
group_items = group_dict["items"]
# Get parenthood per group
representation = io.find_one({"_id": io.ObjectId(repre_id)})
representation = io.find_one({"_id": ObjectId(repre_id)})
if not representation:
not_found["representation"].append(group_items)
not_found_ids.append(repre_id)

View file

@ -2,12 +2,14 @@ import collections
import logging
from Qt import QtWidgets, QtCore
import qtawesome
from bson.objectid import ObjectId
from avalon import io, pipeline
from openpype.pipeline import (
from avalon import io
from openpype.pipeline.load import (
discover_loader_plugins,
switch_container,
get_repres_contexts,
loaders_from_repre_context,
)
from .widgets import (
@ -146,7 +148,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
repre_ids = set()
content_loaders = set()
for item in self._items:
repre_ids.add(io.ObjectId(item["representation"]))
repre_ids.add(ObjectId(item["representation"]))
content_loaders.add(item["loader"])
repres = list(io.find({
@ -369,7 +371,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
loaders = None
for repre_context in repre_contexts.values():
_loaders = set(pipeline.loaders_from_repre_context(
_loaders = set(loaders_from_repre_context(
available_loaders, repre_context
))
if loaders is None:
@ -1306,7 +1308,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
repre_docs_by_parent_id_by_name[parent_id][name] = repre_doc
for container in self._items:
container_repre_id = io.ObjectId(container["representation"])
container_repre_id = ObjectId(container["representation"])
container_repre = self.content_repres[container_repre_id]
container_repre_name = container_repre["name"]

View file

@ -4,14 +4,16 @@ from functools import partial
from Qt import QtWidgets, QtCore
import qtawesome
from bson.objectid import ObjectId
from avalon import io, api
from avalon import io
from openpype import style
from openpype.pipeline import (
HeroVersionType,
update_container,
remove_container,
discover_inventory_actions,
)
from openpype.modules import ModulesManager
from openpype.tools.utils.lib import (
@ -78,7 +80,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
repre_ids = []
for item in items:
item_id = io.ObjectId(item["representation"])
item_id = ObjectId(item["representation"])
if item_id not in repre_ids:
repre_ids.append(item_id)
@ -145,7 +147,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
def _on_switch_to_versioned(items):
repre_ids = []
for item in items:
item_id = io.ObjectId(item["representation"])
item_id = ObjectId(item["representation"])
if item_id not in repre_ids:
repre_ids.append(item_id)
@ -195,7 +197,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
version_doc["name"]
for item in items:
repre_id = io.ObjectId(item["representation"])
repre_id = ObjectId(item["representation"])
version_id = version_id_by_repre_id.get(repre_id)
version_name = version_name_by_id.get(version_id)
if version_name is not None:
@ -487,7 +489,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
containers = containers or [dict()]
# Check which action will be available in the menu
Plugins = api.discover(api.InventoryAction)
Plugins = discover_inventory_actions()
compatible = [p() for p in Plugins if
any(p.is_compatible(c) for c in containers)]
@ -658,7 +660,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
active = items[-1]
# Get available versions for active representation
representation_id = io.ObjectId(active["representation"])
representation_id = ObjectId(active["representation"])
representation = io.find_one({"_id": representation_id})
version = io.find_one({
"_id": representation["parent"]

View file

@ -92,7 +92,8 @@ class CollapsibleWrapper(WrapperWidget):
self.content_layout = content_layout
if self.collapsible:
body_widget.toggle_content(self.collapsed)
if not self.collapsed:
body_widget.toggle_content()
else:
body_widget.hide_toolbox(hide_content=False)

View file

@ -35,7 +35,7 @@ def _iter_model_rows(model,
class AssetModel(TreeModel):
"""A model listing assets in the silo in the active project.
"""A model listing assets in the active project.
The assets are displayed in a treeview, they are visually parented by
a `visualParent` field in the database containing an `_id` to a parent
@ -64,7 +64,7 @@ class AssetModel(TreeModel):
self.refresh()
def _add_hierarchy(self, assets, parent=None, silos=None):
def _add_hierarchy(self, assets, parent=None):
"""Add the assets that are related to the parent as children items.
This method does *not* query the database. These instead are queried
@ -72,27 +72,8 @@ class AssetModel(TreeModel):
queries. Resulting in up to 10x speed increase.
Args:
assets (dict): All assets in the currently active silo stored
by key/value
Returns:
None
assets (dict): All assets from current project.
"""
if silos:
# WARNING: Silo item "_id" is set to silo value
# mainly because GUI issue with preserve selection and expanded row
# and because of easier hierarchy parenting (in "assets")
for silo in silos:
node = Node({
"_id": silo,
"name": silo,
"label": silo,
"type": "silo"
})
self.add_child(node, parent=parent)
self._add_hierarchy(assets, parent=node)
parent_id = parent["_id"] if parent else None
current_assets = assets.get(parent_id, list())
@ -132,27 +113,19 @@ class AssetModel(TreeModel):
self.beginResetModel()
# Get all assets in current silo sorted by name
# Get all assets in current project sorted by name
db_assets = self.dbcon.find({"type": "asset"}).sort("name", 1)
silos = db_assets.distinct("silo") or None
# if any silo is set to None then it's expected it should not be used
if silos and None in silos:
silos = None
# Group the assets by their visual parent's id
assets_by_parent = collections.defaultdict(list)
for asset in db_assets:
parent_id = (
asset.get("data", {}).get("visualParent") or
asset.get("silo")
)
parent_id = asset.get("data", {}).get("visualParent")
assets_by_parent[parent_id].append(asset)
# Build the hierarchical tree items recursively
self._add_hierarchy(
assets_by_parent,
parent=None,
silos=silos
parent=None
)
self.endResetModel()
@ -174,8 +147,6 @@ class AssetModel(TreeModel):
# Allow a custom icon and custom icon color to be defined
data = node.get("_document", {}).get("data", {})
icon = data.get("icon", None)
if icon is None and node.get("type") == "silo":
icon = "database"
color = data.get("color", self._default_asset_icon_color)
if icon is None:

View file

@ -229,7 +229,6 @@ class AssetWidget(QtWidgets.QWidget):
data = {
'project': project['name'],
'asset': asset['name'],
'silo': asset.get("silo"),
'parents': self.get_parents(asset),
'task': task
}

View file

@ -57,7 +57,6 @@ class TextureCopy:
"name": project_name,
"code": project['data']['code']
},
"silo": asset.get('silo'),
"asset": asset['name'],
"family": 'texture',
"subset": 'Main',
@ -155,7 +154,6 @@ def texture_copy(asset, project, path):
t.echo(">>> Initializing avalon session ...")
os.environ["AVALON_PROJECT"] = project
os.environ["AVALON_ASSET"] = asset
os.environ["AVALON_SILO"] = ""
TextureCopy().process(asset, project, path)

View file

@ -61,7 +61,6 @@ def show(root=None, debug=False, parent=None, use_context=True, save=True):
if use_context:
context = {
"asset": api.Session["AVALON_ASSET"],
"silo": api.Session["AVALON_SILO"],
"task": api.Session["AVALON_TASK"]
}
window.set_context(context)