[Automated] Merged develop into main

This commit is contained in:
pypebot 2022-03-05 04:28:31 +01:00 committed by GitHub
commit 722654c2bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 896 additions and 181 deletions

View file

@ -202,13 +202,10 @@ def reload_pipeline(*args):
avalon.api.uninstall()
for module in (
"avalon.io",
"avalon.lib",
"avalon.pipeline",
"avalon.tools.creator.app",
"avalon.tools.manager.app",
"avalon.api",
"avalon.tools",
"avalon.io",
"avalon.lib",
"avalon.pipeline",
"avalon.api",
):
module = importlib.import_module(module)
importlib.reload(module)

View file

@ -5,11 +5,12 @@ import logging
# Pipeline imports
import avalon.api
from avalon import io, pipeline
from avalon import io
from openpype.lib import version_up
from openpype.hosts.fusion import api
from openpype.hosts.fusion.api import lib
from openpype.lib.avalon_context import get_workdir_from_session
log = logging.getLogger("Update Slap Comp")
@ -44,16 +45,6 @@ def _format_version_folder(folder):
return version_folder
def _get_work_folder(session):
"""Convenience function to get the work folder path of the current asset"""
# Get new filename, create path based on asset and work template
template_work = self._project["config"]["template"]["work"]
work_path = pipeline._format_work_template(template_work, session)
return os.path.normpath(work_path)
def _get_fusion_instance():
fusion = getattr(sys.modules["__main__"], "fusion", None)
if fusion is None:
@ -72,7 +63,7 @@ def _format_filepath(session):
asset = session["AVALON_ASSET"]
# Save updated slap comp
work_path = _get_work_folder(session)
work_path = get_workdir_from_session(session)
walk_to_dir = os.path.join(work_path, "scenes", "slapcomp")
slapcomp_dir = os.path.abspath(walk_to_dir)
@ -112,7 +103,7 @@ def _update_savers(comp, session):
None
"""
new_work = _get_work_folder(session)
new_work = get_workdir_from_session(session)
renders = os.path.join(new_work, "renders")
version_folder = _format_version_folder(renders)
renders_version = os.path.join(renders, version_folder)

View file

@ -5,11 +5,12 @@ import logging
from Qt import QtWidgets, QtCore
import avalon.api
from avalon import io, pipeline
from avalon import io
from avalon.vendor import qtawesome as qta
from openpype import style
from openpype.hosts.fusion import api
from openpype.lib.avalon_context import get_workdir_from_session
log = logging.getLogger("Fusion Switch Shot")
@ -123,7 +124,7 @@ class App(QtWidgets.QWidget):
def _on_open_from_dir(self):
start_dir = self._get_context_directory()
start_dir = get_workdir_from_session()
comp_file, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "Choose comp", start_dir)
@ -157,17 +158,6 @@ class App(QtWidgets.QWidget):
import colorbleed.scripts.fusion_switch_shot as switch_shot
switch_shot.switch(asset_name=asset, filepath=file_name, new=True)
def _get_context_directory(self):
project = io.find_one({"type": "project",
"name": avalon.api.Session["AVALON_PROJECT"]},
projection={"config": True})
template = project["config"]["template"]["work"]
dir = pipeline._format_work_template(template, avalon.api.Session)
return dir
def collect_slap_comps(self, directory):
items = glob.glob("{}/*.comp".format(directory))
return items

View file

@ -361,7 +361,7 @@ def zip_and_move(source, destination):
log.debug(f"Saved '{source}' to '{destination}'")
def show(module_name):
def show(tool_name):
"""Call show on "module_name".
This allows to make a QApplication ahead of time and always "exec_" to
@ -375,13 +375,6 @@ def show(module_name):
# requests to be received properly.
time.sleep(1)
# Get tool name from module name
# TODO this is for backwards compatibility not sure if `TB_sceneOpened.js`
# is automatically updated.
# Previous javascript sent 'module_name' which contained whole tool import
# string e.g. "avalon.tools.workfiles" now it should be only "workfiles"
tool_name = module_name.split(".")[-1]
kwargs = {}
if tool_name == "loader":
kwargs["use_context"] = True

View file

@ -37,17 +37,17 @@ class ToolWindows:
def edit_shader_definitions():
from avalon.tools import lib
from Qt import QtWidgets
from openpype.hosts.maya.api.shader_definition_editor import (
ShaderDefinitionsEditor
)
from openpype.tools.utils import qt_app_context
top_level_widgets = QtWidgets.QApplication.topLevelWidgets()
main_window = next(widget for widget in top_level_widgets
if widget.objectName() == "MayaWindow")
with lib.application():
with qt_app_context():
window = ToolWindows.get_window("shader_definition_editor")
if not window:
window = ShaderDefinitionsEditor(parent=main_window)

View file

@ -36,7 +36,7 @@ def install():
return
def deferred():
from avalon.tools import publish
pyblish_icon = host_tools.get_pyblish_icon()
parent_widget = get_main_window()
cmds.menu(
MENU_NAME,
@ -80,7 +80,7 @@ def install():
command=lambda *args: host_tools.show_publish(
parent=parent_widget
),
image=publish.ICON
image=pyblish_icon
)
cmds.menuItem(

View file

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
import os
import maya.cmds as cmds
import maya.cmds as cmds # noqa
from avalon import api
from openpype.api import get_project_settings
from openpype.hosts.maya.api.lib import (
@ -42,20 +43,20 @@ class VRaySceneLoader(api.Loader):
with maintained_selection():
cmds.namespace(addNamespace=namespace)
with namespaced(namespace, new=False):
nodes, group_node = self.create_vray_scene(name,
filename=self.fname)
nodes, root_node = self.create_vray_scene(name,
filename=self.fname)
self[:] = nodes
if not nodes:
return
# colour the group node
presets = get_project_settings(os.environ['AVALON_PROJECT'])
colors = presets['maya']['load']['colors']
settings = get_project_settings(os.environ['AVALON_PROJECT'])
colors = settings['maya']['load']['colors']
c = colors.get(family)
if c is not None:
cmds.setAttr("{0}.useOutlinerColor".format(group_node), 1)
cmds.setAttr("{0}.outlinerColor".format(group_node),
cmds.setAttr("{0}.useOutlinerColor".format(root_node), 1)
cmds.setAttr("{0}.outlinerColor".format(root_node),
(float(c[0])/255),
(float(c[1])/255),
(float(c[2])/255)
@ -123,17 +124,21 @@ class VRaySceneLoader(api.Loader):
mesh_node_name = "VRayScene_{}".format(name)
trans = cmds.createNode(
"transform", name="{}".format(mesh_node_name))
mesh = cmds.createNode(
"mesh", name="{}_Shape".format(mesh_node_name), parent=trans)
"transform", name=mesh_node_name)
vray_scene = cmds.createNode(
"VRayScene", name="{}_VRSCN".format(mesh_node_name), parent=trans)
mesh = cmds.createNode(
"mesh", name="{}_Shape".format(mesh_node_name), parent=trans)
cmds.connectAttr(
"{}.outMesh".format(vray_scene), "{}.inMesh".format(mesh))
cmds.setAttr("{}.FilePath".format(vray_scene), filename, type="string")
# Lock the shape nodes so the user cannot delete these
cmds.lockNode(mesh, lock=True)
cmds.lockNode(vray_scene, lock=True)
# Create important connections
cmds.connectAttr("time1.outTime",
"{0}.inputTime".format(trans))
@ -141,11 +146,9 @@ class VRaySceneLoader(api.Loader):
# Connect mesh to initialShadingGroup
cmds.sets([mesh], forceElement="initialShadingGroup")
group_node = cmds.group(empty=True, name="{}_GRP".format(name))
cmds.parent(trans, group_node)
nodes = [trans, vray_scene, mesh, group_node]
nodes = [trans, vray_scene, mesh]
# Fix: Force refresh so the mesh shows correctly after creation
cmds.refresh()
return nodes, group_node
return nodes, trans

View file

@ -1,6 +1,5 @@
import os
import re
import sys
import six
import platform
import contextlib
@ -679,10 +678,10 @@ def get_render_path(node):
}
nuke_imageio_writes = get_created_node_imageio_setting(**data_preset)
host_name = os.environ.get("AVALON_APP")
application = lib.get_application(os.environ["AVALON_APP_NAME"])
data.update({
"application": application,
"app": host_name,
"nuke_imageio_writes": nuke_imageio_writes
})
@ -805,18 +804,14 @@ def create_write_node(name, data, input=None, prenodes=None,
'''
imageio_writes = get_created_node_imageio_setting(**data)
app_manager = ApplicationManager()
app_name = os.environ.get("AVALON_APP_NAME")
if app_name:
app = app_manager.applications.get(app_name)
for knob in imageio_writes["knobs"]:
if knob["name"] == "file_type":
representation = knob["value"]
host_name = os.environ.get("AVALON_APP")
try:
data.update({
"app": app.host_name,
"app": host_name,
"imageio_writes": imageio_writes,
"representation": representation,
})

View file

@ -446,6 +446,8 @@ class ExporterReviewMov(ExporterReview):
return path
def generate_mov(self, farm=False, **kwargs):
reformat_node_add = kwargs["reformat_node_add"]
reformat_node_config = kwargs["reformat_node_config"]
bake_viewer_process = kwargs["bake_viewer_process"]
bake_viewer_input_process_node = kwargs[
"bake_viewer_input_process"]
@ -483,6 +485,30 @@ class ExporterReviewMov(ExporterReview):
self.previous_node = r_node
self.log.debug("Read... `{}`".format(self._temp_nodes[subset]))
# add reformat node
if reformat_node_add:
# append reformated tag
add_tags.append("reformated")
rf_node = nuke.createNode("Reformat")
for kn_conf in reformat_node_config:
_type = kn_conf["type"]
k_name = str(kn_conf["name"])
k_value = kn_conf["value"]
# to remove unicode as nuke doesn't like it
if _type == "string":
k_value = str(kn_conf["value"])
rf_node[k_name].setValue(k_value)
# connect
rf_node.setInput(0, self.previous_node)
self._temp_nodes[subset].append(rf_node)
self.previous_node = rf_node
self.log.debug(
"Reformat... `{}`".format(self._temp_nodes[subset]))
# only create colorspace baking if toggled on
if bake_viewer_process:
if bake_viewer_input_process_node:

View file

@ -1,4 +1,5 @@
import os
import re
import pyblish.api
import openpype
from openpype.hosts.nuke.api import plugin
@ -25,6 +26,7 @@ class ExtractReviewDataMov(openpype.api.Extractor):
def process(self, instance):
families = instance.data["families"]
task_type = instance.context.data["taskType"]
subset = instance.data["subset"]
self.log.info("Creating staging dir...")
if "representations" not in instance.data:
@ -46,6 +48,7 @@ class ExtractReviewDataMov(openpype.api.Extractor):
for o_name, o_data in self.outputs.items():
f_families = o_data["filter"]["families"]
f_task_types = o_data["filter"]["task_types"]
f_subsets = o_data["filter"]["sebsets"]
# test if family found in context
test_families = any([
@ -69,11 +72,25 @@ class ExtractReviewDataMov(openpype.api.Extractor):
bool(not f_task_types)
])
# test subsets from filter
test_subsets = any([
# check if any of subset filter inputs
# converted to regex patern is not found in subset
# we keep strict case sensitivity
bool(next((
s for s in f_subsets
if re.search(re.compile(s), subset)
), None)),
# but if no subsets were set then make this acuntable too
bool(not f_subsets)
])
# we need all filters to be positive for this
# preset to be activated
test_all = all([
test_families,
test_task_types
test_task_types,
test_subsets
])
# if it is not positive then skip this preset
@ -120,6 +137,13 @@ class ExtractReviewDataMov(openpype.api.Extractor):
if generated_repres:
# assign to representations
instance.data["representations"] += generated_repres
else:
instance.data["families"].remove("review")
self.log.info((
"Removing `review` from families. "
"Not available baking profile."
))
self.log.debug(instance.data["families"])
self.log.debug(
"_ representations: {}".format(

View file

@ -81,14 +81,10 @@ class CollectTextures(pyblish.api.ContextPlugin):
parsed_subset = instance.data["subset"].replace(
instance.data["family"], '')
fill_pairs = {
explicit_data = {
"subset": parsed_subset
}
fill_pairs = prepare_template_data(fill_pairs)
workfile_subset = format_template_with_optional_keys(
fill_pairs, self.workfile_subset_template)
processed_instance = False
for repre in instance.data["representations"]:
ext = repre["ext"].replace('.', '')
@ -102,6 +98,21 @@ class CollectTextures(pyblish.api.ContextPlugin):
if ext in self.main_workfile_extensions or \
ext in self.other_workfile_extensions:
formatting_data = self._get_parsed_groups(
repre_file,
self.input_naming_patterns["workfile"],
self.input_naming_groups["workfile"],
self.color_space
)
self.log.info("Parsed groups from workfile "
"name '{}': {}".format(repre_file,
formatting_data))
formatting_data.update(explicit_data)
fill_pairs = prepare_template_data(formatting_data)
workfile_subset = format_template_with_optional_keys(
fill_pairs, self.workfile_subset_template)
asset_build = self._get_asset_build(
repre_file,
self.input_naming_patterns["workfile"],
@ -148,11 +159,23 @@ class CollectTextures(pyblish.api.ContextPlugin):
resource_files[workfile_subset].append(item)
if ext in self.texture_extensions:
formatting_data = self._get_parsed_groups(
repre_file,
self.input_naming_patterns["textures"],
self.input_naming_groups["textures"],
self.color_space
)
self.log.info("Parsed groups from texture "
"name '{}': {}".format(repre_file,
formatting_data))
c_space = self._get_color_space(
repre_file,
self.color_space
)
# optional value
channel = self._get_channel_name(
repre_file,
self.input_naming_patterns["textures"],
@ -160,6 +183,7 @@ class CollectTextures(pyblish.api.ContextPlugin):
self.color_space
)
# optional value
shader = self._get_shader_name(
repre_file,
self.input_naming_patterns["textures"],
@ -167,13 +191,15 @@ class CollectTextures(pyblish.api.ContextPlugin):
self.color_space
)
formatting_data = {
explicit_data = {
"color_space": c_space or '', # None throws exception
"channel": channel or '',
"shader": shader or '',
"subset": parsed_subset or ''
}
formatting_data.update(explicit_data)
fill_pairs = prepare_template_data(formatting_data)
subset = format_template_with_optional_keys(
fill_pairs, self.texture_subset_template)
@ -243,6 +269,13 @@ class CollectTextures(pyblish.api.ContextPlugin):
for asset_build, version, subset, family in asset_builds:
if not main_version:
main_version = version
try:
version_int = int(version or main_version or 1)
except ValueError:
self.log.error("Parsed version {} is not "
"an number".format(version))
new_instance = context.create_instance(subset)
new_instance.data.update(
{
@ -251,7 +284,7 @@ class CollectTextures(pyblish.api.ContextPlugin):
"label": subset,
"name": subset,
"family": family,
"version": int(version or main_version or 1),
"version": version_int,
"asset_build": asset_build # remove in validator
}
)
@ -320,13 +353,14 @@ class CollectTextures(pyblish.api.ContextPlugin):
"""
asset_name = "NOT_AVAIL"
return self._parse(name, input_naming_patterns, input_naming_groups,
color_spaces, 'asset') or asset_name
return (self._parse_key(name, input_naming_patterns,
input_naming_groups, color_spaces, 'asset') or
asset_name)
def _get_version(self, name, input_naming_patterns, input_naming_groups,
color_spaces):
found = self._parse(name, input_naming_patterns, input_naming_groups,
color_spaces, 'version')
found = self._parse_key(name, input_naming_patterns,
input_naming_groups, color_spaces, 'version')
if found:
return found.replace('v', '')
@ -336,8 +370,8 @@ class CollectTextures(pyblish.api.ContextPlugin):
def _get_udim(self, name, input_naming_patterns, input_naming_groups,
color_spaces):
"""Parses from 'name' udim value."""
found = self._parse(name, input_naming_patterns, input_naming_groups,
color_spaces, 'udim')
found = self._parse_key(name, input_naming_patterns,
input_naming_groups, color_spaces, 'udim')
if found:
return found
@ -375,12 +409,15 @@ class CollectTextures(pyblish.api.ContextPlugin):
Unknown format of channel name and color spaces >> cs are known
list - 'color_space' used as a placeholder
"""
found = self._parse(name, input_naming_patterns, input_naming_groups,
color_spaces, 'shader')
if found:
return found
found = None
try:
found = self._parse_key(name, input_naming_patterns,
input_naming_groups, color_spaces,
'shader')
except ValueError:
self.log.warning("Didn't find shader in {}".format(name))
self.log.warning("Didn't find shader in {}".format(name))
return found
def _get_channel_name(self, name, input_naming_patterns,
input_naming_groups, color_spaces):
@ -389,15 +426,18 @@ class CollectTextures(pyblish.api.ContextPlugin):
Unknown format of channel name and color spaces >> cs are known
list - 'color_space' used as a placeholder
"""
found = self._parse(name, input_naming_patterns, input_naming_groups,
color_spaces, 'channel')
if found:
return found
found = None
try:
found = self._parse_key(name, input_naming_patterns,
input_naming_groups, color_spaces,
'channel')
except ValueError:
self.log.warning("Didn't find channel in {}".format(name))
self.log.warning("Didn't find channel in {}".format(name))
return found
def _parse(self, name, input_naming_patterns, input_naming_groups,
color_spaces, key):
def _parse_key(self, name, input_naming_patterns, input_naming_groups,
color_spaces, key):
"""Universal way to parse 'name' with configurable regex groups.
Args:
@ -411,23 +451,47 @@ class CollectTextures(pyblish.api.ContextPlugin):
Raises:
ValueError - if broken 'input_naming_groups'
"""
parsed_groups = self._get_parsed_groups(name,
input_naming_patterns,
input_naming_groups,
color_spaces)
try:
parsed_value = parsed_groups[key]
return parsed_value
except (IndexError, KeyError):
msg = ("'Textures group positions' must " +
"have '{}' key".format(key))
raise ValueError(msg)
def _get_parsed_groups(self, name, input_naming_patterns,
input_naming_groups, color_spaces):
"""Universal way to parse 'name' with configurable regex groups.
Args:
name (str): workfile name or texture name
input_naming_patterns (list):
[workfile_pattern] or [texture_pattern]
input_naming_groups (list)
ordinal position of regex groups matching to input_naming..
color_spaces (list) - predefined color spaces
Returns:
(dict) {group_name:parsed_value}
"""
for input_pattern in input_naming_patterns:
for cs in color_spaces:
pattern = input_pattern.replace('{color_space}', cs)
regex_result = re.findall(pattern, name)
if regex_result:
idx = list(input_naming_groups).index(key)
if idx < 0:
msg = "input_naming_groups must " +\
"have '{}' key".format(key)
raise ValueError(msg)
if len(regex_result[0]) == len(input_naming_groups):
return dict(zip(input_naming_groups, regex_result[0]))
else:
self.log.warning("No of parsed groups doesn't match "
"no of group labels")
try:
parsed_value = regex_result[0][idx]
return parsed_value
except IndexError:
self.log.warning("Wrong index, probably "
"wrong name {}".format(name))
raise ValueError("Name '{}' cannot be parsed by any "
"'{}' patterns".format(name, input_naming_patterns))
def _update_representations(self, upd_representations):
"""Frames dont have sense for textures, add collected udims instead."""

View file

@ -644,6 +644,166 @@ def get_workdir(
)
def template_data_from_session(session=None):
""" Return dictionary with template from session keys.
Args:
session (dict, Optional): The Session to use. If not provided use the
currently active global Session.
Returns:
dict: All available data from session.
"""
from avalon import io
import avalon.api
if session is None:
session = avalon.api.Session
project_name = session["AVALON_PROJECT"]
project_doc = io._database[project_name].find_one({"type": "project"})
asset_doc = io._database[project_name].find_one({
"type": "asset",
"name": session["AVALON_ASSET"]
})
task_name = session["AVALON_TASK"]
host_name = session["AVALON_APP"]
return get_workdir_data(project_doc, asset_doc, task_name, host_name)
def compute_session_changes(
session, task=None, asset=None, app=None, template_key=None
):
"""Compute the changes for a Session object on asset, task or app switch
This does *NOT* update the Session object, but returns the changes
required for a valid update of the Session.
Args:
session (dict): The initial session to compute changes to.
This is required for computing the full Work Directory, as that
also depends on the values that haven't changed.
task (str, Optional): Name of task to switch to.
asset (str or dict, Optional): Name of asset to switch to.
You can also directly provide the Asset dictionary as returned
from the database to avoid an additional query. (optimization)
app (str, Optional): Name of app to switch to.
Returns:
dict: The required changes in the Session dictionary.
"""
changes = dict()
# If no changes, return directly
if not any([task, asset, app]):
return changes
# Get asset document and asset
asset_document = None
asset_tasks = None
if isinstance(asset, dict):
# Assume asset database document
asset_document = asset
asset_tasks = asset_document.get("data", {}).get("tasks")
asset = asset["name"]
if not asset_document or not asset_tasks:
from avalon import io
# Assume asset name
asset_document = io.find_one(
{
"name": asset,
"type": "asset"
},
{"data.tasks": True}
)
assert asset_document, "Asset must exist"
# Detect any changes compared session
mapping = {
"AVALON_ASSET": asset,
"AVALON_TASK": task,
"AVALON_APP": app,
}
changes = {
key: value
for key, value in mapping.items()
if value and value != session.get(key)
}
if not changes:
return changes
# Compute work directory (with the temporary changed session so far)
_session = session.copy()
_session.update(changes)
changes["AVALON_WORKDIR"] = get_workdir_from_session(_session)
return changes
def get_workdir_from_session(session=None, template_key=None):
import avalon.api
if session is None:
session = avalon.api.Session
project_name = session["AVALON_PROJECT"]
host_name = session["AVALON_APP"]
anatomy = Anatomy(project_name)
template_data = template_data_from_session(session)
anatomy_filled = anatomy.format(template_data)
if not template_key:
task_type = template_data["task"]["type"]
template_key = get_workfile_template_key(
task_type,
host_name,
project_name=project_name
)
return anatomy_filled[template_key]["folder"]
def update_current_task(task=None, asset=None, app=None, template_key=None):
"""Update active Session to a new task work area.
This updates the live Session to a different `asset`, `task` or `app`.
Args:
task (str): The task to set.
asset (str): The asset to set.
app (str): The app to set.
Returns:
dict: The changed key, values in the current Session.
"""
import avalon.api
from avalon.pipeline import emit
changes = compute_session_changes(
avalon.api.Session,
task=task,
asset=asset,
app=app,
template_key=template_key
)
# Update the Session and environments. Pop from environments all keys with
# value set to None.
for key, value in changes.items():
avalon.api.Session[key] = value
if value is None:
os.environ.pop(key, None)
else:
os.environ[key] = value
# Emit session change
emit("taskChanged", changes.copy())
return changes
@with_avalon
def get_workfile_doc(asset_id, task_name, filename, dbcon=None):
"""Return workfile document for entered context.
@ -952,7 +1112,7 @@ class BuildWorkfile:
Returns:
(dict): preset per entered task name
"""
host_name = avalon.api.registered_host().__name__.rsplit(".", 1)[-1]
host_name = os.environ["AVALON_APP"]
project_settings = get_project_settings(
avalon.io.Session["AVALON_PROJECT"]
)

View file

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
import os
import tempfile
import time
from datetime import datetime
import subprocess
import json
import platform
import uuid
from Deadline.Scripting import RepositoryUtils, FileUtils
@ -36,9 +37,11 @@ def inject_openpype_environment(deadlinePlugin):
print("--- OpenPype executable: {}".format(openpype_app))
# tempfile.TemporaryFile cannot be used because of locking
export_url = os.path.join(tempfile.gettempdir(),
time.strftime('%Y%m%d%H%M%S'),
'env.json') # add HHMMSS + delete later
temp_file_name = "{}_{}.json".format(
datetime.utcnow().strftime('%Y%m%d%H%M%S%f'),
str(uuid.uuid1())
)
export_url = os.path.join(tempfile.gettempdir(), temp_file_name)
print(">>> Temporary path: {}".format(export_url))
args = [

View file

@ -20,11 +20,16 @@ from openpype_modules.ftrack.lib import (
query_custom_attributes,
CUST_ATTR_ID_KEY,
CUST_ATTR_AUTO_SYNC,
FPS_KEYS,
avalon_sync,
BaseEvent
)
from openpype_modules.ftrack.lib.avalon_sync import (
convert_to_fps,
InvalidFpsValue
)
from openpype.lib import CURRENT_DOC_SCHEMAS
@ -1149,12 +1154,31 @@ class SyncToAvalonEvent(BaseEvent):
"description": ftrack_ent["description"]
}
}
invalid_fps_items = []
cust_attrs = self.get_cust_attr_values(ftrack_ent)
for key, val in cust_attrs.items():
if key.startswith("avalon_"):
continue
if key in FPS_KEYS:
try:
val = convert_to_fps(val)
except InvalidFpsValue:
invalid_fps_items.append((ftrack_ent["id"], val))
continue
final_entity["data"][key] = val
if invalid_fps_items:
fps_msg = (
"These entities have invalid fps value in custom attributes"
)
items = []
for entity_id, value in invalid_fps_items:
ent_path = self.get_ent_path(entity_id)
items.append("{} - \"{}\"".format(ent_path, value))
self.report_items["error"][fps_msg] = items
_mongo_id_str = cust_attrs.get(CUST_ATTR_ID_KEY)
if _mongo_id_str:
try:
@ -2155,11 +2179,19 @@ class SyncToAvalonEvent(BaseEvent):
)
convert_types_by_id[attr_id] = convert_type
default_value = attr["default"]
if key in FPS_KEYS:
try:
default_value = convert_to_fps(default_value)
except InvalidFpsValue:
pass
entities_dict[ftrack_project_id]["hier_attrs"][key] = (
attr["default"]
)
# PREPARE DATA BEFORE THIS
invalid_fps_items = []
avalon_hier = []
for item in values:
value = item["value"]
@ -2173,8 +2205,25 @@ class SyncToAvalonEvent(BaseEvent):
if convert_type:
value = convert_type(value)
if key in FPS_KEYS:
try:
value = convert_to_fps(value)
except InvalidFpsValue:
invalid_fps_items.append((entity_id, value))
continue
entities_dict[entity_id]["hier_attrs"][key] = value
if invalid_fps_items:
fps_msg = (
"These entities have invalid fps value in custom attributes"
)
items = []
for entity_id, value in invalid_fps_items:
ent_path = self.get_ent_path(entity_id)
items.append("{} - \"{}\"".format(ent_path, value))
self.report_items["error"][fps_msg] = items
# Get dictionary with not None hierarchical values to pull to childs
project_values = {}
for key, value in (

View file

@ -11,6 +11,7 @@ from openpype_modules.ftrack.lib import (
CUST_ATTR_TOOLS,
CUST_ATTR_APPLICATIONS,
CUST_ATTR_INTENT,
FPS_KEYS,
default_custom_attributes_definition,
app_definitions_from_app_manager,
@ -519,20 +520,28 @@ class CustomAttributes(BaseAction):
self.show_message(event, msg)
def process_attribute(self, data):
existing_attrs = self.session.query(
"CustomAttributeConfiguration"
).all()
existing_attrs = self.session.query((
"select is_hierarchical, key, type, entity_type, object_type_id"
" from CustomAttributeConfiguration"
)).all()
matching = []
is_hierarchical = data.get("is_hierarchical", False)
for attr in existing_attrs:
if (
attr["key"] != data["key"] or
attr["type"]["name"] != data["type"]["name"]
is_hierarchical != attr["is_hierarchical"]
or attr["key"] != data["key"]
):
continue
if data.get("is_hierarchical") is True:
if attr["is_hierarchical"] is True:
matching.append(attr)
if attr["type"]["name"] != data["type"]["name"]:
if data["key"] in FPS_KEYS and attr["type"]["name"] == "text":
self.log.info("Kept 'fps' as text custom attribute.")
return
continue
if is_hierarchical:
matching.append(attr)
elif "object_type_id" in data:
if (
attr["entity_type"] == data["entity_type"] and

View file

@ -4,7 +4,8 @@ from .constants import (
CUST_ATTR_GROUP,
CUST_ATTR_TOOLS,
CUST_ATTR_APPLICATIONS,
CUST_ATTR_INTENT
CUST_ATTR_INTENT,
FPS_KEYS
)
from .settings import (
get_ftrack_event_mongo_info
@ -30,6 +31,8 @@ __all__ = (
"CUST_ATTR_GROUP",
"CUST_ATTR_TOOLS",
"CUST_ATTR_APPLICATIONS",
"CUST_ATTR_INTENT",
"FPS_KEYS",
"get_ftrack_event_mongo_info",

View file

@ -2,6 +2,9 @@ import re
import json
import collections
import copy
import numbers
import six
from avalon.api import AvalonMongoDB
@ -14,7 +17,7 @@ from openpype.api import (
)
from openpype.lib import ApplicationManager
from .constants import CUST_ATTR_ID_KEY
from .constants import CUST_ATTR_ID_KEY, FPS_KEYS
from .custom_attributes import get_openpype_attr, query_custom_attributes
from bson.objectid import ObjectId
@ -33,6 +36,106 @@ CURRENT_DOC_SCHEMAS = {
}
class InvalidFpsValue(Exception):
pass
def is_string_number(value):
"""Can string value be converted to number (float)."""
if not isinstance(value, six.string_types):
raise TypeError("Expected {} got {}".format(
", ".join(str(t) for t in six.string_types), str(type(value))
))
if value == ".":
return False
if value.startswith("."):
value = "0" + value
elif value.endswith("."):
value = value + "0"
if re.match(r"^\d+(\.\d+)?$", value) is None:
return False
return True
def convert_to_fps(source_value):
"""Convert value into fps value.
Non string values are kept untouched. String is tried to convert.
Valid values:
"1000"
"1000.05"
"1000,05"
",05"
".05"
"1000,"
"1000."
"1000/1000"
"1000.05/1000"
"1000/1000.05"
"1000.05/1000.05"
"1000,05/1000"
"1000/1000,05"
"1000,05/1000,05"
Invalid values:
"/"
"/1000"
"1000/"
","
"."
...any other string
Returns:
float: Converted value.
Raises:
InvalidFpsValue: When value can't be converted to float.
"""
if not isinstance(source_value, six.string_types):
if isinstance(source_value, numbers.Number):
return float(source_value)
return source_value
value = source_value.strip().replace(",", ".")
if not value:
raise InvalidFpsValue("Got empty value")
subs = value.split("/")
if len(subs) == 1:
str_value = subs[0]
if not is_string_number(str_value):
raise InvalidFpsValue(
"Value \"{}\" can't be converted to number.".format(value)
)
return float(str_value)
elif len(subs) == 2:
divident, divisor = subs
if not divident or not is_string_number(divident):
raise InvalidFpsValue(
"Divident value \"{}\" can't be converted to number".format(
divident
)
)
if not divisor or not is_string_number(divisor):
raise InvalidFpsValue(
"Divisor value \"{}\" can't be converted to number".format(
divident
)
)
divisor_float = float(divisor)
if divisor_float == 0.0:
raise InvalidFpsValue("Can't divide by zero")
return float(divident) / divisor_float
raise InvalidFpsValue(
"Value can't be converted to number \"{}\"".format(source_value)
)
def create_chunks(iterable, chunk_size=None):
"""Separate iterable into multiple chunks by size.
@ -980,6 +1083,7 @@ class SyncEntitiesFactory:
sync_ids
)
invalid_fps_items = []
for item in items:
entity_id = item["entity_id"]
attr_id = item["configuration_id"]
@ -992,8 +1096,24 @@ class SyncEntitiesFactory:
value = item["value"]
if convert_type:
value = convert_type(value)
if key in FPS_KEYS:
try:
value = convert_to_fps(value)
except InvalidFpsValue:
invalid_fps_items.append((entity_id, value))
self.entities_dict[entity_id][store_key][key] = value
if invalid_fps_items:
fps_msg = (
"These entities have invalid fps value in custom attributes"
)
items = []
for entity_id, value in invalid_fps_items:
ent_path = self.get_ent_path(entity_id)
items.append("{} - \"{}\"".format(ent_path, value))
self.report_items["error"][fps_msg] = items
# process hierarchical attributes
self.set_hierarchical_attribute(
hier_attrs, sync_ids, cust_attr_type_name_by_id
@ -1026,8 +1146,15 @@ class SyncEntitiesFactory:
if key.startswith("avalon_"):
store_key = "avalon_attrs"
default_value = attr["default"]
if key in FPS_KEYS:
try:
default_value = convert_to_fps(default_value)
except InvalidFpsValue:
pass
self.entities_dict[self.ft_project_id][store_key][key] = (
attr["default"]
default_value
)
# Add attribute ids to entities dictionary
@ -1069,6 +1196,7 @@ class SyncEntitiesFactory:
True
)
invalid_fps_items = []
avalon_hier = []
for item in items:
value = item["value"]
@ -1088,6 +1216,13 @@ class SyncEntitiesFactory:
entity_id = item["entity_id"]
key = attribute_key_by_id[attr_id]
if key in FPS_KEYS:
try:
value = convert_to_fps(value)
except InvalidFpsValue:
invalid_fps_items.append((entity_id, value))
continue
if key.startswith("avalon_"):
store_key = "avalon_attrs"
avalon_hier.append(key)
@ -1095,6 +1230,16 @@ class SyncEntitiesFactory:
store_key = "hier_attrs"
self.entities_dict[entity_id][store_key][key] = value
if invalid_fps_items:
fps_msg = (
"These entities have invalid fps value in custom attributes"
)
items = []
for entity_id, value in invalid_fps_items:
ent_path = self.get_ent_path(entity_id)
items.append("{} - \"{}\"".format(ent_path, value))
self.report_items["error"][fps_msg] = items
# Get dictionary with not None hierarchical values to pull to childs
top_id = self.ft_project_id
project_values = {}

View file

@ -12,3 +12,9 @@ CUST_ATTR_APPLICATIONS = "applications"
CUST_ATTR_TOOLS = "tools_env"
# Intent custom attribute name
CUST_ATTR_INTENT = "intent"
FPS_KEYS = {
"fps",
# For development purposes
"fps_string"
}

View file

@ -399,15 +399,6 @@ class CreatedInstance:
self._data["active"] = data.get("active", True)
self._data["creator_identifier"] = creator.identifier
# QUESTION handle version of instance here or in creator?
version = None
if not new:
version = data.get("version")
if version is None:
version = 1
self._data["version"] = version
# Pop from source data all keys that are defined in `_data` before
# this moment and through their values away
# - they should be the same and if are not then should not change

View file

@ -34,7 +34,12 @@ class ExtractJpegEXR(pyblish.api.InstancePlugin):
self.log.info("subset {}".format(instance.data['subset']))
# skip crypto passes.
if 'crypto' in instance.data['subset']:
# TODO: This is just a quick fix and has its own side-effects - it is
# affecting every subset name with `crypto` in its name.
# This must be solved properly, maybe using tags on
# representation that can be determined much earlier and
# with better precision.
if 'crypto' in instance.data['subset'].lower():
self.log.info("Skipping crypto passes.")
return

View file

@ -1171,6 +1171,9 @@ class ExtractReview(pyblish.api.InstancePlugin):
self.log.debug("input_width: `{}`".format(input_width))
self.log.debug("input_height: `{}`".format(input_height))
reformat_in_baking = bool("reformated" in new_repre["tags"])
self.log.debug("reformat_in_baking: `{}`".format(reformat_in_baking))
# Use instance resolution if output definition has not set it.
if output_width is None or output_height is None:
output_width = temp_data["resolution_width"]
@ -1182,6 +1185,17 @@ class ExtractReview(pyblish.api.InstancePlugin):
output_width = input_width
output_height = input_height
if reformat_in_baking:
self.log.debug((
"Using resolution from input. It is already "
"reformated from baking process"
))
output_width = input_width
output_height = input_height
pixel_aspect = 1
new_repre["resolutionWidth"] = input_width
new_repre["resolutionHeight"] = input_height
output_width = int(output_width)
output_height = int(output_height)

View file

@ -4,13 +4,15 @@ import sys
import logging
# Pipeline imports
from avalon import api, io, pipeline
from avalon import api, io
import avalon.fusion
# Config imports
import openpype.lib as pype
import openpype.hosts.fusion.lib as fusion_lib
from openpype.lib.avalon_context import get_workdir_from_session
log = logging.getLogger("Update Slap Comp")
self = sys.modules[__name__]
@ -44,16 +46,6 @@ def _format_version_folder(folder):
return version_folder
def _get_work_folder(session):
"""Convenience function to get the work folder path of the current asset"""
# Get new filename, create path based on asset and work template
template_work = self._project["config"]["template"]["work"]
work_path = pipeline._format_work_template(template_work, session)
return os.path.normpath(work_path)
def _get_fusion_instance():
fusion = getattr(sys.modules["__main__"], "fusion", None)
if fusion is None:
@ -72,7 +64,7 @@ def _format_filepath(session):
asset = session["AVALON_ASSET"]
# Save updated slap comp
work_path = _get_work_folder(session)
work_path = get_workdir_from_session(session)
walk_to_dir = os.path.join(work_path, "scenes", "slapcomp")
slapcomp_dir = os.path.abspath(walk_to_dir)
@ -103,7 +95,7 @@ def _update_savers(comp, session):
None
"""
new_work = _get_work_folder(session)
new_work = get_workdir_from_session(session)
renders = os.path.join(new_work, "renders")
version_folder = _format_version_folder(renders)
renders_version = os.path.join(renders, version_folder)

View file

@ -589,6 +589,12 @@
12,
255
],
"vrayscene_layer": [
255,
150,
12,
255
],
"yeticache": [
99,
206,

View file

@ -116,13 +116,42 @@
"baking": {
"filter": {
"task_types": [],
"families": []
"families": [],
"sebsets": []
},
"extension": "mov",
"viewer_process_override": "",
"bake_viewer_process": true,
"bake_viewer_input_process": true,
"add_tags": []
"add_tags": [],
"reformat_node_add": false,
"reformat_node_config": [
{
"type": "string",
"name": "type",
"value": "to format"
},
{
"type": "string",
"name": "format",
"value": "HD_1080"
},
{
"type": "string",
"name": "filter",
"value": "Lanczos6"
},
{
"type": "bool",
"name": "black_outside",
"value": true
},
{
"type": "bool",
"name": "pbb",
"value": false
}
]
}
}
},

View file

@ -584,8 +584,9 @@ class DictConditionalEntity(ItemEntity):
self.enum_entity.update_default_value(enum_value)
for children_by_key in self.non_gui_children.values():
value_copy = copy.deepcopy(value)
for key, child_obj in children_by_key.items():
child_value = value.get(key, NOT_SET)
child_value = value_copy.get(key, NOT_SET)
child_obj.update_default_value(child_value)
def update_studio_value(self, value):
@ -620,8 +621,9 @@ class DictConditionalEntity(ItemEntity):
self.enum_entity.update_studio_value(enum_value)
for children_by_key in self.non_gui_children.values():
value_copy = copy.deepcopy(value)
for key, child_obj in children_by_key.items():
child_value = value.get(key, NOT_SET)
child_value = value_copy.get(key, NOT_SET)
child_obj.update_studio_value(child_value)
def update_project_value(self, value):
@ -656,8 +658,9 @@ class DictConditionalEntity(ItemEntity):
self.enum_entity.update_project_value(enum_value)
for children_by_key in self.non_gui_children.values():
value_copy = copy.deepcopy(value)
for key, child_obj in children_by_key.items():
child_value = value.get(key, NOT_SET)
child_value = value_copy.get(key, NOT_SET)
child_obj.update_project_value(child_value)
def _discard_changes(self, on_change_trigger):

View file

@ -75,6 +75,11 @@
"label": "Vray Proxy:",
"key": "vrayproxy"
},
{
"type": "color",
"label": "Vray Scene:",
"key": "vrayscene_layer"
},
{
"type": "color",
"label": "Yeti Cache:",

View file

@ -195,6 +195,12 @@
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "sebsets",
"label": "Subsets",
"type": "list",
"object_type": "text"
}
]
},
@ -226,6 +232,121 @@
"label": "Add additional tags to representations",
"type": "list",
"object_type": "text"
},
{
"type": "separator"
},
{
"type": "boolean",
"key": "reformat_node_add",
"label": "Add Reformat Node",
"default": false
},
{
"type": "collapsible-wrap",
"label": "Reformat Node Knobs",
"collapsible": true,
"collapsed": false,
"children": [
{
"type": "list",
"key": "reformat_node_config",
"object_type": {
"type": "dict-conditional",
"enum_key": "type",
"enum_label": "Type",
"enum_children": [
{
"key": "string",
"label": "String",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "text",
"key": "value",
"label": "Value"
}
]
},
{
"key": "bool",
"label": "Boolean",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "boolean",
"key": "value",
"label": "Value"
}
]
},
{
"key": "number",
"label": "Number",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "list-strict",
"key": "value",
"label": "Value",
"object_types": [
{
"type": "number",
"key": "number",
"default": 1,
"decimal": 4
}
]
}
]
},
{
"key": "list_numbers",
"label": "2 Numbers",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "list-strict",
"key": "value",
"label": "Value",
"object_types": [
{
"type": "number",
"key": "x",
"default": 1,
"decimal": 4
},
{
"type": "number",
"key": "y",
"default": 1,
"decimal": 4
}
]
}
]
}
]
}
}
]
}
]
}

View file

@ -4,9 +4,11 @@ from subprocess import Popen
import ftrack_api
from Qt import QtWidgets, QtCore
from openpype import style
from openpype.api import get_current_project_settings
from openpype.lib.avalon_context import update_current_task
from openpype.tools.utils.lib import qt_app_context
from avalon import io, api, style, schema
from avalon import io, api, schema
from . import widget, model
module = sys.modules[__name__]
@ -463,12 +465,12 @@ class Window(QtWidgets.QDialog):
return
task_name = task_model.itemData(index)[0]
try:
api.update_current_task(task=task_name, asset=asset_name)
update_current_task(task=task_name, asset=asset_name)
self.open_app()
finally:
if origin_task is not None and origin_asset is not None:
api.update_current_task(
update_current_task(
task=origin_task, asset=origin_asset
)

View file

@ -3,9 +3,11 @@ from collections import defaultdict
from Qt import QtWidgets, QtCore
# TODO: expose this better in avalon core
from avalon.tools import lib
from avalon.tools.models import TreeModel
from openpype.tools.utils.models import TreeModel
from openpype.tools.utils.lib import (
preserve_expanded_rows,
preserve_selection,
)
from .models import (
AssetModel,
@ -88,8 +90,8 @@ class AssetOutliner(QtWidgets.QWidget):
"""Add all items from the current scene"""
items = []
with lib.preserve_expanded_rows(self.view):
with lib.preserve_selection(self.view):
with preserve_expanded_rows(self.view):
with preserve_selection(self.view):
self.clear()
nodes = commands.get_all_asset_nodes()
items = commands.create_items_from_nodes(nodes)
@ -100,8 +102,8 @@ class AssetOutliner(QtWidgets.QWidget):
def get_selected_assets(self):
"""Add all selected items from the current scene"""
with lib.preserve_expanded_rows(self.view):
with lib.preserve_selection(self.view):
with preserve_expanded_rows(self.view):
with preserve_selection(self.view):
self.clear()
nodes = commands.get_selected_nodes()
items = commands.create_items_from_nodes(nodes)

View file

@ -8,7 +8,7 @@ from avalon import api, io, style, schema
from avalon.vendor import qtawesome
from avalon.lib import HeroVersionType
from avalon.tools.models import TreeModel, Item
from openpype.tools.utils.models import TreeModel, Item
from .lib import (
get_site_icons,

View file

@ -7,9 +7,13 @@ from Qt import QtWidgets, QtCore
from avalon import io, api, style
from avalon.vendor import qtawesome
from avalon.lib import HeroVersionType
from avalon.tools import lib as tools_lib
from openpype.modules import ModulesManager
from openpype.tools.utils.lib import (
get_progress_for_repre,
iter_model_rows,
format_version
)
from .switch_dialog import SwitchAssetDialog
from .model import InventoryModel
@ -20,12 +24,12 @@ DEFAULT_COLOR = "#fb9c15"
log = logging.getLogger("SceneInventory")
class SceneInvetoryView(QtWidgets.QTreeView):
class SceneInventoryView(QtWidgets.QTreeView):
data_changed = QtCore.Signal()
hierarchy_view_changed = QtCore.Signal(bool)
def __init__(self, parent=None):
super(SceneInvetoryView, self).__init__(parent=parent)
super(SceneInventoryView, self).__init__(parent=parent)
# view settings
self.setIndentation(12)
@ -373,7 +377,7 @@ class SceneInvetoryView(QtWidgets.QTreeView):
if not repre_doc:
continue
progress = tools_lib.get_progress_for_repre(
progress = get_progress_for_repre(
repre_doc,
active_site,
remote_site
@ -544,7 +548,7 @@ class SceneInvetoryView(QtWidgets.QTreeView):
"toggle": selection_model.Toggle,
}[options.get("mode", "select")]
for item in tools_lib.iter_model_rows(model, 0):
for item in iter_model_rows(model, 0):
item = item.data(InventoryModel.ItemRole)
if item.get("isGroupNode"):
continue
@ -704,7 +708,7 @@ class SceneInvetoryView(QtWidgets.QTreeView):
labels = []
for version in all_versions:
is_hero = version["type"] == "hero_version"
label = tools_lib.format_version(version["name"], is_hero)
label = format_version(version["name"], is_hero)
labels.append(label)
versions_by_label[label] = version["name"]
@ -792,3 +796,40 @@ class SceneInvetoryView(QtWidgets.QTreeView):
).format(version_str)
dialog.setText(msg)
dialog.exec_()
def update_all(self):
"""Update all items that are currently 'outdated' in the view"""
# Get the source model through the proxy model
model = self.model().sourceModel()
# Get all items from outdated groups
outdated_items = []
for index in iter_model_rows(model,
column=0,
include_root=False):
item = index.data(model.ItemRole)
if not item.get("isGroupNode"):
continue
# Only the group nodes contain the "highest_version" data and as
# such we find only the groups and take its children.
if not model.outdated(item):
continue
# Collect all children which we want to update
children = item.children()
outdated_items.extend(children)
if not outdated_items:
log.info("Nothing to update.")
return
# Trigger update to latest
for item in outdated_items:
try:
api.update(item, -1)
except AssertionError:
self._show_version_error_dialog(None, [item])
log.warning("Update failed", exc_info=True)
self.data_changed.emit()

View file

@ -18,7 +18,7 @@ from .model import (
InventoryModel,
FilterProxyModel
)
from .view import SceneInvetoryView
from .view import SceneInventoryView
module = sys.modules[__name__]
@ -54,14 +54,21 @@ class SceneInventoryWindow(QtWidgets.QDialog):
outdated_only_checkbox.setToolTip("Show outdated files only")
outdated_only_checkbox.setChecked(False)
icon = qtawesome.icon("fa.arrow-up", color="white")
update_all_button = QtWidgets.QPushButton(self)
update_all_button.setToolTip("Update all outdated to latest version")
update_all_button.setIcon(icon)
icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton(self)
update_all_button.setToolTip("Refresh")
refresh_button.setIcon(icon)
control_layout = QtWidgets.QHBoxLayout()
control_layout.addWidget(filter_label)
control_layout.addWidget(text_filter)
control_layout.addWidget(outdated_only_checkbox)
control_layout.addWidget(update_all_button)
control_layout.addWidget(refresh_button)
# endregion control
@ -73,7 +80,7 @@ class SceneInventoryWindow(QtWidgets.QDialog):
proxy.setDynamicSortFilter(True)
proxy.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
view = SceneInvetoryView(self)
view = SceneInventoryView(self)
view.setModel(proxy)
# set some nice default widths for the view
@ -98,11 +105,13 @@ class SceneInventoryWindow(QtWidgets.QDialog):
self._on_outdated_state_change
)
view.hierarchy_view_changed.connect(
self._on_hiearchy_view_change
self._on_hierarchy_view_change
)
view.data_changed.connect(self.refresh)
refresh_button.clicked.connect(self.refresh)
update_all_button.clicked.connect(self._on_update_all)
self._update_all_button = update_all_button
self._outdated_only_checkbox = outdated_only_checkbox
self._view = view
self._model = model
@ -146,7 +155,7 @@ class SceneInventoryWindow(QtWidgets.QDialog):
kwargs["selected"] = self._view._selected
self._model.refresh(**kwargs)
def _on_hiearchy_view_change(self, enabled):
def _on_hierarchy_view_change(self, enabled):
self._proxy.set_hierarchy_view(enabled)
self._model.set_hierarchy_view(enabled)
@ -158,6 +167,9 @@ class SceneInventoryWindow(QtWidgets.QDialog):
self._outdated_only_checkbox.isChecked()
)
def _on_update_all(self):
self._view.update_all()
def show(root=None, debug=False, parent=None, items=None):
"""Display Scene Inventory GUI

View file

@ -3,10 +3,10 @@ import sys
import openpype
import pyblish.api
from openpype.tools.utils.host_tools import show_publish
def main(env):
from avalon.tools import publish
# Registers pype's Global pyblish plugins
openpype.install()
@ -19,7 +19,7 @@ def main(env):
continue
pyblish.api.register_plugin_path(path)
return publish.show()
return show_publish()
if __name__ == "__main__":

View file

@ -15,6 +15,7 @@ from .lib import (
get_warning_pixmap,
set_style_property,
DynamicQThread,
qt_app_context,
)
from .models import (
@ -39,6 +40,7 @@ __all__ = (
"get_warning_pixmap",
"set_style_property",
"DynamicQThread",
"qt_app_context",
"RecursiveSortFilterProxyModel",
)

View file

@ -3,8 +3,9 @@
It is possible to create `HostToolsHelper` in host implementation or
use singleton approach with global functions (using helper anyway).
"""
import os
import avalon.api
import pyblish.api
from .lib import qt_app_context
@ -196,10 +197,29 @@ class HostToolsHelper:
library_loader_tool.refresh()
def show_publish(self, parent=None):
"""Publish UI."""
from avalon.tools import publish
"""Try showing the most desirable publish GUI
publish.show(parent)
This function cycles through the currently registered
graphical user interfaces, if any, and presents it to
the user.
"""
pyblish_show = self._discover_pyblish_gui()
return pyblish_show(parent)
def _discover_pyblish_gui(self):
"""Return the most desirable of the currently registered GUIs"""
# Prefer last registered
guis = list(reversed(pyblish.api.registered_guis()))
for gui in guis:
try:
gui = __import__(gui).show
except (ImportError, AttributeError):
continue
else:
return gui
raise ImportError("No Pyblish GUI found")
def get_look_assigner_tool(self, parent):
"""Create, cache and return look assigner tool window."""
@ -394,3 +414,11 @@ def show_publish(parent=None):
def show_experimental_tools_dialog(parent=None):
_SingletonPoint.show_tool_by_name("experimental_tools", parent)
def get_pyblish_icon():
pyblish_dir = os.path.abspath(os.path.dirname(pyblish.api.__file__))
icon_path = os.path.join(pyblish_dir, "icons", "logo-32x32.svg")
if os.path.exists(icon_path):
return icon_path
return None

View file

@ -29,6 +29,10 @@ from openpype.lib import (
create_workdir_extra_folders,
get_system_general_anatomy_data
)
from openpype.lib.avalon_context import (
update_current_task,
compute_session_changes
)
from .model import FilesModel
from .view import FilesView
@ -667,7 +671,7 @@ class FilesWidget(QtWidgets.QWidget):
session["AVALON_APP"],
project_name=session["AVALON_PROJECT"]
)
changes = pipeline.compute_session_changes(
changes = compute_session_changes(
session,
asset=self._get_asset_doc(),
task=self._task_name,
@ -681,7 +685,7 @@ class FilesWidget(QtWidgets.QWidget):
"""Enter the asset and task session currently selected"""
session = api.Session.copy()
changes = pipeline.compute_session_changes(
changes = compute_session_changes(
session,
asset=self._get_asset_doc(),
task=self._task_name,
@ -692,7 +696,7 @@ class FilesWidget(QtWidgets.QWidget):
# to avoid any unwanted Task Changed callbacks to be triggered.
return
api.update_current_task(
update_current_task(
asset=self._get_asset_doc(),
task=self._task_name,
template_key=self.template_key

View file

@ -1,11 +1,11 @@
import os
import logging
from Qt import QtCore, QtGui
from Qt import QtCore
from avalon import style
from avalon.vendor import qtawesome
from avalon.tools.models import TreeModel, Item
from openpype.tools.utils.models import TreeModel, Item
log = logging.getLogger(__name__)