Merge remote-tracking branch 'origin/develop' into develop

This commit is contained in:
Milan Kolar 2021-09-23 09:11:36 +01:00
commit 15dbd9fcac
23 changed files with 284 additions and 133 deletions

View file

@ -378,6 +378,17 @@ def add_otio_metadata(otio_item, media_source, **kwargs):
def create_otio_timeline():
def set_prev_item(itemindex, track_item):
# Add Gap if needed
if itemindex == 0:
# if it is first track item at track then add
# it to previouse item
return track_item
else:
# get previouse item
return track_item.parent().items()[itemindex - 1]
# get current timeline
self.timeline = hiero.ui.activeSequence()
self.project_fps = self.timeline.framerate().toFloat()
@ -396,14 +407,6 @@ def create_otio_timeline():
type(track), track.name())
for itemindex, track_item in enumerate(track):
# skip offline track items
if not track_item.isMediaPresent():
continue
# skip if track item is disabled
if not track_item.isEnabled():
continue
# Add Gap if needed
if itemindex == 0:
# if it is first track item at track then add

View file

@ -131,7 +131,7 @@ class PrecollectInstances(pyblish.api.ContextPlugin):
self.create_shot_instance(context, **data)
self.log.info("Creating instance: {}".format(instance))
self.log.debug(
self.log.info(
"_ instance.data: {}".format(pformat(instance.data)))
if not with_audio:

View file

@ -8,6 +8,7 @@ from openpype.hosts.hiero.otio import hiero_export
from Qt.QtGui import QPixmap
import tempfile
class PrecollectWorkfile(pyblish.api.ContextPlugin):
"""Inject the current working file into context"""

View file

@ -35,6 +35,7 @@ def install():
pyblish.register_plugin_path(PUBLISH_PATH)
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
avalon.register_plugin_path(avalon.InventoryAction, INVENTORY_PATH)
log.info(PUBLISH_PATH)
menu.install()

View file

@ -4,6 +4,53 @@ import avalon.maya
from openpype.api import PypeCreatorMixin
def get_reference_node(members, log=None):
"""Get the reference node from the container members
Args:
members: list of node names
Returns:
str: Reference node name.
"""
from maya import cmds
# Collect the references without .placeHolderList[] attributes as
# unique entries (objects only) and skipping the sharedReferenceNode.
references = set()
for ref in cmds.ls(members, exactType="reference", objectsOnly=True):
# Ignore any `:sharedReferenceNode`
if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"):
continue
# Ignore _UNKNOWN_REF_NODE_ (PLN-160)
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
continue
references.add(ref)
assert references, "No reference node found in container"
# Get highest reference node (least parents)
highest = min(references,
key=lambda x: len(get_reference_node_parents(x)))
# Warn the user when we're taking the highest reference node
if len(references) > 1:
if not log:
from openpype.lib import PypeLogger
log = PypeLogger().get_logger(__name__)
log.warning("More than one reference node found in "
"container, using highest reference node: "
"%s (in: %s)", highest, list(references))
return highest
def get_reference_node_parents(ref):
"""Return all parent reference nodes of reference node
@ -109,7 +156,7 @@ class ReferenceLoader(api.Loader):
loader=self.__class__.__name__
))
else:
ref_node = self._get_reference_node(nodes)
ref_node = get_reference_node(nodes, self.log)
loaded_containers.append(containerise(
name=name,
namespace=namespace,
@ -126,46 +173,6 @@ class ReferenceLoader(api.Loader):
"""To be implemented by subclass"""
raise NotImplementedError("Must be implemented by subclass")
def _get_reference_node(self, members):
"""Get the reference node from the container members
Args:
members: list of node names
Returns:
str: Reference node name.
"""
from maya import cmds
# Collect the references without .placeHolderList[] attributes as
# unique entries (objects only) and skipping the sharedReferenceNode.
references = set()
for ref in cmds.ls(members, exactType="reference", objectsOnly=True):
# Ignore any `:sharedReferenceNode`
if ref.rsplit(":", 1)[-1].startswith("sharedReferenceNode"):
continue
# Ignore _UNKNOWN_REF_NODE_ (PLN-160)
if ref.rsplit(":", 1)[-1].startswith("_UNKNOWN_REF_NODE_"):
continue
references.add(ref)
assert references, "No reference node found in container"
# Get highest reference node (least parents)
highest = min(references,
key=lambda x: len(get_reference_node_parents(x)))
# Warn the user when we're taking the highest reference node
if len(references) > 1:
self.log.warning("More than one reference node found in "
"container, using highest reference node: "
"%s (in: %s)", highest, list(references))
return highest
def update(self, container, representation):
@ -178,7 +185,7 @@ class ReferenceLoader(api.Loader):
# Get reference node from container members
members = cmds.sets(node, query=True, nodesOnly=True)
reference_node = self._get_reference_node(members)
reference_node = get_reference_node(members, self.log)
file_type = {
"ma": "mayaAscii",
@ -274,7 +281,7 @@ class ReferenceLoader(api.Loader):
# Assume asset has been referenced
members = cmds.sets(node, query=True)
reference_node = self._get_reference_node(members)
reference_node = get_reference_node(members, self.log)
assert reference_node, ("Imported container not supported; "
"container must be referenced.")

View file

@ -0,0 +1,29 @@
from maya import cmds
from avalon import api
from openpype.hosts.maya.api.plugin import get_reference_node
class ImportReference(api.InventoryAction):
"""Imports selected reference to inside of the file."""
label = "Import Reference"
icon = "download"
color = "#d8d8d8"
def process(self, containers):
references = cmds.ls(type="reference")
for container in containers:
if container["loader"] != "ReferenceLoader":
print("Not a reference, skipping")
continue
node = container["objectName"]
members = cmds.sets(node, query=True, nodesOnly=True)
ref_node = get_reference_node(members)
ref_file = cmds.referenceQuery(ref_node, f=True)
cmds.file(ref_file, importReference=True)
return True # return anything to trigger model refresh

View file

@ -9,7 +9,7 @@ class IncrementScriptVersion(pyblish.api.ContextPlugin):
order = pyblish.api.IntegratorOrder + 0.9
label = "Increment Script Version"
optional = True
families = ["workfile", "render", "render.local", "render.farm"]
families = ["workfile"]
hosts = ['nuke']
def process(self, context):

View file

@ -10,16 +10,14 @@ from .constants import (
from openpype.modules import OpenPypeModule
from openpype_interfaces import (
ITrayModule,
IPluginPaths,
IFtrackEventHandlerPaths
IPluginPaths
)
class ClockifyModule(
OpenPypeModule,
ITrayModule,
IPluginPaths,
IFtrackEventHandlerPaths
IPluginPaths
):
name = "clockify"
@ -93,8 +91,8 @@ class ClockifyModule(
"actions": [actions_path]
}
def get_event_handler_paths(self):
"""Implementaton of IFtrackEventHandlerPaths to get plugin paths."""
def get_ftrack_event_handler_paths(self):
"""Function for Ftrack module to add ftrack event handler paths."""
return {
"user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH]

View file

@ -8,8 +8,7 @@ from openpype_interfaces import (
ITrayModule,
IPluginPaths,
ILaunchHookPaths,
ISettingsChangeListener,
IFtrackEventHandlerPaths
ISettingsChangeListener
)
from openpype.settings import SaveWarningExc
@ -81,9 +80,17 @@ class FtrackModule(
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:
if not isinstance(module, IFtrackEventHandlerPaths):
if not hasattr(module, "get_ftrack_event_handler_paths"):
continue
paths_by_type = module.get_event_handler_paths() or {}
try:
paths_by_type = module.get_ftrack_event_handler_paths()
except Exception:
continue
if not isinstance(paths_by_type, dict):
continue
for key, value in paths_by_type.items():
if not value:
continue

View file

@ -1,12 +0,0 @@
from abc import abstractmethod
from openpype.modules import OpenPypeInterface
class IFtrackEventHandlerPaths(OpenPypeInterface):
"""Other modules interface to return paths to ftrack event handlers.
Expected output is dictionary with "server" and "user" keys.
"""
@abstractmethod
def get_event_handler_paths(self):
pass

View file

@ -5,8 +5,7 @@ from .constants import (
CUST_ATTR_TOOLS,
CUST_ATTR_APPLICATIONS
)
from . settings import (
get_ftrack_url_from_settings,
from .settings import (
get_ftrack_event_mongo_info
)
from .custom_attributes import (
@ -31,7 +30,6 @@ __all__ = (
"CUST_ATTR_TOOLS",
"CUST_ATTR_APPLICATIONS",
"get_ftrack_url_from_settings",
"get_ftrack_event_mongo_info",
"default_custom_attributes_definition",

View file

@ -1,13 +1,4 @@
import os
from openpype.api import get_system_settings
def get_ftrack_settings():
return get_system_settings()["modules"]["ftrack"]
def get_ftrack_url_from_settings():
return get_ftrack_settings()["ftrack_server"]
def get_ftrack_event_mongo_info():

View file

@ -209,7 +209,7 @@ class TimersManager(OpenPypeModule, ITrayService, IIdleManager):
self.widget_user_idle.refresh_context()
self.is_running = False
self.timer_stopper(None)
self.timer_stopped(None)
def connect_with_modules(self, enabled_modules):
for module in enabled_modules:

View file

@ -13,7 +13,7 @@ class CollectHierarchy(pyblish.api.ContextPlugin):
"""
label = "Collect Hierarchy"
order = pyblish.api.CollectorOrder - 0.57
order = pyblish.api.CollectorOrder - 0.47
families = ["shot"]
hosts = ["resolve", "hiero"]

View file

@ -18,7 +18,7 @@ class CollectOcioFrameRanges(pyblish.api.InstancePlugin):
Adding timeline and source ranges to instance data"""
label = "Collect OTIO Frame Ranges"
order = pyblish.api.CollectorOrder - 0.58
order = pyblish.api.CollectorOrder - 0.48
families = ["shot", "clip"]
hosts = ["resolve", "hiero"]

View file

@ -20,7 +20,7 @@ class CollectOcioReview(pyblish.api.InstancePlugin):
"""Get matching otio track from defined review layer"""
label = "Collect OTIO Review"
order = pyblish.api.CollectorOrder - 0.57
order = pyblish.api.CollectorOrder - 0.47
families = ["clip"]
hosts = ["resolve", "hiero"]

View file

@ -18,7 +18,7 @@ class CollectOcioSubsetResources(pyblish.api.InstancePlugin):
"""Get Resources for a subset version"""
label = "Collect OTIO Subset Resources"
order = pyblish.api.CollectorOrder - 0.57
order = pyblish.api.CollectorOrder - 0.47
families = ["clip"]
hosts = ["resolve", "hiero"]

View file

@ -1,34 +0,0 @@
import pyblish.api
import os
import subprocess
import openpype.lib
try:
import os.errno as errno
except ImportError:
import errno
class ValidateFFmpegInstalled(pyblish.api.ContextPlugin):
"""Validate availability of ffmpeg tool in PATH"""
order = pyblish.api.ValidatorOrder
label = 'Validate ffmpeg installation'
optional = True
def is_tool(self, name):
try:
devnull = open(os.devnull, "w")
subprocess.Popen(
[name], stdout=devnull, stderr=devnull
).communicate()
except OSError as e:
if e.errno == errno.ENOENT:
return False
return True
def process(self, context):
ffmpeg_path = openpype.lib.get_ffmpeg_tool_path("ffmpeg")
self.log.info("ffmpeg path: `{}`".format(ffmpeg_path))
if self.is_tool("{}".format(ffmpeg_path)) is False:
self.log.error("ffmpeg not found in PATH")
raise RuntimeError('ffmpeg not installed.')

View file

@ -102,6 +102,11 @@
},
"ExtractSlateFrame": {
"viewer_lut_raw": false
},
"IncrementScriptVersion": {
"enabled": true,
"optional": true,
"active": true
}
},
"load": {

View file

@ -173,6 +173,38 @@
"label": "Viewer LUT raw"
}
]
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Integrators"
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "IncrementScriptVersion",
"label": "IncrementScriptVersion",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
}
]
}
]
}

View file

@ -15,6 +15,11 @@ from openpype.lib import is_admin_password_required
from openpype.widgets import PasswordDialog
from openpype import resources
from openpype.api import (
get_project_basic_paths,
create_project_folders,
Logger
)
from avalon.api import AvalonMongoDB
@ -24,10 +29,15 @@ class ProjectManagerWindow(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ProjectManagerWindow, self).__init__(parent)
self.log = Logger.get_logger(self.__class__.__name__)
self._initial_reset = False
self._password_dialog = None
self._user_passed = False
# keep track of the current project PM is viewing
self._current_project = None
self.setWindowTitle("OpenPype Project Manager")
self.setWindowIcon(QtGui.QIcon(resources.get_openpype_icon_filepath()))
@ -57,12 +67,18 @@ class ProjectManagerWindow(QtWidgets.QWidget):
create_project_btn = QtWidgets.QPushButton(
"Create project...", project_widget
)
create_folders_btn = QtWidgets.QPushButton(
ResourceCache.get_icon("asset", "default"),
"Create Starting Folders",
project_widget
)
project_layout = QtWidgets.QHBoxLayout(project_widget)
project_layout.setContentsMargins(0, 0, 0, 0)
project_layout.addWidget(project_combobox, 0)
project_layout.addWidget(refresh_projects_btn, 0)
project_layout.addWidget(create_project_btn, 0)
project_layout.addWidget(create_folders_btn)
project_layout.addStretch(1)
# Helper buttons
@ -124,6 +140,7 @@ class ProjectManagerWindow(QtWidgets.QWidget):
refresh_projects_btn.clicked.connect(self._on_project_refresh)
create_project_btn.clicked.connect(self._on_project_create)
create_folders_btn.clicked.connect(self._on_create_folders)
project_combobox.currentIndexChanged.connect(self._on_project_change)
save_btn.clicked.connect(self._on_save_click)
add_asset_btn.clicked.connect(self._on_add_asset)
@ -139,6 +156,7 @@ class ProjectManagerWindow(QtWidgets.QWidget):
self._refresh_projects_btn = refresh_projects_btn
self._project_combobox = project_combobox
self._create_project_btn = create_project_btn
self._create_folders_btn = create_folders_btn
self._add_asset_btn = add_asset_btn
self._add_task_btn = add_task_btn
@ -179,7 +197,9 @@ class ProjectManagerWindow(QtWidgets.QWidget):
self._set_project(self._project_combobox.currentText())
def _on_project_change(self):
self._set_project(self._project_combobox.currentText())
if self._project_combobox.currentIndex() != 0:
self._current_project = self._project_combobox.currentText()
self._set_project(self._current_project)
def _on_project_refresh(self):
self.refresh_projects()
@ -193,6 +213,29 @@ class ProjectManagerWindow(QtWidgets.QWidget):
def _on_add_task(self):
self.hierarchy_view.add_task()
def _on_create_folders(self):
if not self._current_project:
return
qm = QtWidgets.QMessageBox
ans = qm.question(self,
"OpenPype Project Manager",
"Confirm to create starting project folders?",
qm.Yes | qm.No)
if ans == qm.Yes:
try:
# Get paths based on presets
basic_paths = get_project_basic_paths(self._current_project)
if not basic_paths:
pass
# Invoking OpenPype API to create the project folders
create_project_folders(basic_paths, self._current_project)
except Exception as exc:
self.log.warning(
"Cannot create starting folders: {}".format(exc),
exc_info=True
)
def show_message(self, message):
# TODO add nicer message pop
self.message_label.setText(message)
@ -203,9 +246,11 @@ class ProjectManagerWindow(QtWidgets.QWidget):
if dialog.result() != 1:
return
project_name = dialog.project_name
self.show_message("Created project \"{}\"".format(project_name))
self.refresh_projects(project_name)
self._current_project = dialog.project_name
self.show_message(
"Created project \"{}\"".format(self._current_project)
)
self.refresh_projects(self._current_project)
def _show_password_dialog(self):
if self._password_dialog:

View file

@ -96,6 +96,7 @@ Attributes:
import os
import re
import sys
import platform
import traceback
import subprocess
import site
@ -339,6 +340,80 @@ def set_modules_environments():
os.environ.update(env)
def is_tool(name):
try:
import os.errno as errno
except ImportError:
import errno
try:
devnull = open(os.devnull, "w")
subprocess.Popen(
[name], stdout=devnull, stderr=devnull
).communicate()
except OSError as exc:
if exc.errno == errno.ENOENT:
return False
return True
def _startup_validations():
"""Validations before OpenPype starts."""
try:
_validate_thirdparty_binaries()
except Exception as exc:
if os.environ.get("OPENPYPE_HEADLESS_MODE"):
raise
import tkinter
from tkinter.messagebox import showerror
root = tkinter.Tk()
root.attributes("-alpha", 0.0)
root.wm_state("iconic")
if platform.system().lower() != "windows":
root.withdraw()
showerror(
"Startup validations didn't pass",
str(exc)
)
root.withdraw()
sys.exit(1)
def _validate_thirdparty_binaries():
"""Check existence of thirdpart executables."""
low_platform = platform.system().lower()
binary_vendors_dir = os.path.join(
os.environ["OPENPYPE_ROOT"],
"vendor",
"bin"
)
error_msg = (
"Missing binary dependency {}. Please fetch thirdparty dependencies."
)
# Validate existence of FFmpeg
ffmpeg_dir = os.path.join(binary_vendors_dir, "ffmpeg", low_platform)
if low_platform == "windows":
ffmpeg_dir = os.path.join(ffmpeg_dir, "bin")
ffmpeg_executable = os.path.join(ffmpeg_dir, "ffmpeg")
if not is_tool(ffmpeg_executable):
raise RuntimeError(error_msg.format("FFmpeg"))
# Validate existence of OpenImageIO (not on MacOs)
if low_platform != "darwin":
oiio_tool_path = os.path.join(
binary_vendors_dir,
"oiio",
low_platform,
"oiiotool"
)
if not is_tool(oiio_tool_path):
raise RuntimeError(error_msg.format("OpenImageIO"))
def _process_arguments() -> tuple:
"""Process command line arguments.
@ -767,6 +842,11 @@ def boot():
# ------------------------------------------------------------------------
os.environ["OPENPYPE_ROOT"] = OPENPYPE_ROOT
# ------------------------------------------------------------------------
# Do necessary startup validations
# ------------------------------------------------------------------------
_startup_validations()
# ------------------------------------------------------------------------
# Play animation
# ------------------------------------------------------------------------

View file

@ -6594,9 +6594,9 @@ prism-react-renderer@^1.1.1:
integrity sha512-GHqzxLYImx1iKN1jJURcuRoA/0ygCcNhfGw1IT8nPIMzarmKQ3Nc+JcG0gi8JXQzuh0C5ShE4npMIoqNin40hg==
prismjs@^1.23.0:
version "1.24.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.24.0.tgz#0409c30068a6c52c89ef7f1089b3ca4de56be2ac"
integrity sha512-SqV5GRsNqnzCL8k5dfAjCNhUrF3pR0A9lTDSCUZeh/LIshheXJEaP0hwLz2t4XHivd2J/v2HR+gRnigzeKe3cQ==
version "1.25.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756"
integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg==
process-nextick-args@~2.0.0:
version "2.0.1"