Merge branch 'develop' into feature/OP_2395_env_groups

This commit is contained in:
iLLiCiTiT 2021-12-20 14:36:59 +01:00
commit a61e3f70e7
11 changed files with 226 additions and 90 deletions

View file

@ -1,6 +1,6 @@
# Changelog
## [3.7.0-nightly.8](https://github.com/pypeclub/OpenPype/tree/HEAD)
## [3.7.0-nightly.9](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.6.4...HEAD)
@ -14,6 +14,8 @@
**🚀 Enhancements**
- Settings UI: Hyperlinks to settings [\#2420](https://github.com/pypeclub/OpenPype/pull/2420)
- Modules: JobQueue module moved one hierarchy level higher [\#2419](https://github.com/pypeclub/OpenPype/pull/2419)
- Ftrack: Check existence of object type on recreation [\#2404](https://github.com/pypeclub/OpenPype/pull/2404)
- Flame: moving `utility\_scripts` to api folder also with `scripts` [\#2385](https://github.com/pypeclub/OpenPype/pull/2385)
- Centos 7 dependency compatibility [\#2384](https://github.com/pypeclub/OpenPype/pull/2384)
@ -29,13 +31,14 @@
- General: OpenPype default modules hierarchy [\#2338](https://github.com/pypeclub/OpenPype/pull/2338)
- General: FFprobe error exception contain original error message [\#2328](https://github.com/pypeclub/OpenPype/pull/2328)
- Resolve: Add experimental button to menu [\#2325](https://github.com/pypeclub/OpenPype/pull/2325)
- Input links: Cleanup and unification of differences [\#2322](https://github.com/pypeclub/OpenPype/pull/2322)
- General: Don't validate vendor bin with executing them [\#2317](https://github.com/pypeclub/OpenPype/pull/2317)
- General: Multilayer EXRs support [\#2315](https://github.com/pypeclub/OpenPype/pull/2315)
- General: Reduce vendor imports [\#2305](https://github.com/pypeclub/OpenPype/pull/2305)
- Ftrack: Synchronize input links [\#2287](https://github.com/pypeclub/OpenPype/pull/2287)
**🐛 Bug fixes**
- PS: Introduced settings for invalid characters to use in ValidateNaming plugin [\#2417](https://github.com/pypeclub/OpenPype/pull/2417)
- Settings UI: Breadcrumbs path does not create new entities [\#2416](https://github.com/pypeclub/OpenPype/pull/2416)
- AfterEffects: Variant 2022 is in defaults but missing in schemas [\#2412](https://github.com/pypeclub/OpenPype/pull/2412)
- General: Fix access to environments from default settings [\#2403](https://github.com/pypeclub/OpenPype/pull/2403)
- Fix: Placeholder Input color set fix [\#2399](https://github.com/pypeclub/OpenPype/pull/2399)
- Settings: Fix state change of wrapper label [\#2396](https://github.com/pypeclub/OpenPype/pull/2396)
@ -55,8 +58,7 @@
- Tools: Use Qt context on tools show [\#2340](https://github.com/pypeclub/OpenPype/pull/2340)
- Flame: Fix default argument value in custom dictionary [\#2339](https://github.com/pypeclub/OpenPype/pull/2339)
- Timers Manager: Disable auto stop timer on linux platform [\#2334](https://github.com/pypeclub/OpenPype/pull/2334)
- nuke: bake preset single input exception [\#2331](https://github.com/pypeclub/OpenPype/pull/2331)
- Hiero: fixing multiple templates at a hierarchy parent [\#2330](https://github.com/pypeclub/OpenPype/pull/2330)
- Fix - provider icons are pulled from a folder [\#2326](https://github.com/pypeclub/OpenPype/pull/2326)
- Royal Render: Fix plugin order and OpenPype auto-detection [\#2291](https://github.com/pypeclub/OpenPype/pull/2291)
**Merged pull requests:**
@ -66,7 +68,6 @@
- Linux : flip updating submodules logic [\#2357](https://github.com/pypeclub/OpenPype/pull/2357)
- Update of avalon-core [\#2346](https://github.com/pypeclub/OpenPype/pull/2346)
- Maya: configurable model top level validation [\#2321](https://github.com/pypeclub/OpenPype/pull/2321)
- Create test publish class for After Effects [\#2270](https://github.com/pypeclub/OpenPype/pull/2270)
## [3.6.4](https://github.com/pypeclub/OpenPype/tree/3.6.4) (2021-11-23)
@ -88,17 +89,6 @@
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.2-nightly.2...3.6.2)
**🚀 Enhancements**
- Tools: Assets widget [\#2265](https://github.com/pypeclub/OpenPype/pull/2265)
- SceneInventory: Choose loader in asset switcher [\#2262](https://github.com/pypeclub/OpenPype/pull/2262)
**🐛 Bug fixes**
- Tools: Parenting of tools in Nuke and Hiero [\#2266](https://github.com/pypeclub/OpenPype/pull/2266)
- limiting validator to specific editorial hosts [\#2264](https://github.com/pypeclub/OpenPype/pull/2264)
- Tools: Select Context dialog attribute fix [\#2261](https://github.com/pypeclub/OpenPype/pull/2261)
## [3.6.1](https://github.com/pypeclub/OpenPype/tree/3.6.1) (2021-11-16)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.6.1-nightly.1...3.6.1)

View file

@ -731,6 +731,10 @@ class LaunchHook:
def app_name(self):
return getattr(self.application, "full_name", None)
@property
def modules_manager(self):
return getattr(self.launch_context, "modules_manager", None)
def validate(self):
"""Optional validation of launch hook on initialization.
@ -793,9 +797,13 @@ class ApplicationLaunchContext:
"""
def __init__(self, application, executable, env_group=None, **data):
from openpype.modules import ModulesManager
# Application object
self.application = application
self.modules_manager = ModulesManager()
# Logger
logger_name = "{}-{}".format(self.__class__.__name__, self.app_name)
self.log = PypeLogger.get_logger(logger_name)
@ -908,10 +916,7 @@ class ApplicationLaunchContext:
paths.append(path)
# Load modules paths
from openpype.modules import ModulesManager
manager = ModulesManager()
paths.extend(manager.collect_launch_hook_paths())
paths.extend(self.modules_manager.collect_launch_hook_paths())
return paths

View file

@ -1433,7 +1433,11 @@ def get_creator_by_name(creator_name, case_sensitive=False):
@with_avalon
def change_timer_to_current_context():
"""Called after context change to change timers"""
"""Called after context change to change timers.
TODO:
- use TimersManager's static method instead of reimplementing it here
"""
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
if not webserver_url:
log.warning("Couldn't find webserver url")
@ -1448,8 +1452,7 @@ def change_timer_to_current_context():
data = {
"project_name": avalon.io.Session["AVALON_PROJECT"],
"asset_name": avalon.io.Session["AVALON_ASSET"],
"task_name": avalon.io.Session["AVALON_TASK"],
"hierarchy": get_hierarchy()
"task_name": avalon.io.Session["AVALON_TASK"]
}
requests.post(rest_api_url, json=data)

View file

@ -52,7 +52,7 @@ class PostFtrackHook(PostLaunchHook):
)
if entity:
self.ftrack_status_change(session, entity, project_name)
self.start_timer(session, entity, ftrack_api)
except Exception:
self.log.warning(
"Couldn't finish Ftrack procedure.", exc_info=True
@ -160,26 +160,3 @@ class PostFtrackHook(PostLaunchHook):
" on Ftrack entity type \"{}\""
).format(next_status_name, entity.entity_type)
self.log.warning(msg)
def start_timer(self, session, entity, _ftrack_api):
"""Start Ftrack timer on task from context."""
self.log.debug("Triggering timer start.")
user_entity = session.query("User where username is \"{}\"".format(
os.environ["FTRACK_API_USER"]
)).first()
if not user_entity:
self.log.warning(
"Couldn't find user with username \"{}\" in Ftrack".format(
os.environ["FTRACK_API_USER"]
)
)
return
try:
user_entity.start_timer(entity, force=True)
session.commit()
self.log.debug("Timer start triggered successfully.")
except Exception:
self.log.warning("Couldn't trigger Ftrack timer.", exc_info=True)

View file

@ -0,0 +1,3 @@
class InvalidContextError(ValueError):
"""Context for which the timer should be started is invalid."""
pass

View file

@ -0,0 +1,45 @@
from openpype.lib import PostLaunchHook
class PostStartTimerHook(PostLaunchHook):
"""Start timer with TimersManager module.
This module requires enabled TimerManager module.
"""
order = None
def execute(self):
project_name = self.data.get("project_name")
asset_name = self.data.get("asset_name")
task_name = self.data.get("task_name")
missing_context_keys = set()
if not project_name:
missing_context_keys.add("project_name")
if not asset_name:
missing_context_keys.add("asset_name")
if not task_name:
missing_context_keys.add("task_name")
if missing_context_keys:
missing_keys_str = ", ".join([
"\"{}\"".format(key) for key in missing_context_keys
])
self.log.debug("Hook {} skipped. Missing data keys: {}".format(
self.__class__.__name__, missing_keys_str
))
return
timers_manager = self.modules_manager.modules_by_name.get(
"timers_manager"
)
if not timers_manager or not timers_manager.enabled:
self.log.info((
"Skipping starting timer because"
" TimersManager is not available."
))
return
timers_manager.start_timer_with_webserver(
project_name, asset_name, task_name, logger=self.log
)

View file

@ -39,17 +39,23 @@ class TimersManagerModuleRestApi:
async def start_timer(self, request):
data = await request.json()
try:
project_name = data['project_name']
asset_name = data['asset_name']
task_name = data['task_name']
hierarchy = data['hierarchy']
project_name = data["project_name"]
asset_name = data["asset_name"]
task_name = data["task_name"]
except KeyError:
log.error("Payload must contain fields 'project_name, " +
"'asset_name', 'task_name', 'hierarchy'")
return Response(status=400)
msg = (
"Payload must contain fields 'project_name,"
" 'asset_name' and 'task_name'"
)
log.error(msg)
return Response(status=400, message=msg)
self.module.stop_timers()
self.module.start_timer(project_name, asset_name, task_name, hierarchy)
try:
self.module.start_timer(project_name, asset_name, task_name)
except Exception as exc:
return Response(status=404, message=str(exc))
return Response(status=200)
async def stop_timer(self, request):

View file

@ -1,9 +1,15 @@
import os
import platform
from openpype.modules import OpenPypeModule
from openpype_interfaces import ITrayService
from avalon.api import AvalonMongoDB
from openpype.modules import OpenPypeModule
from openpype_interfaces import (
ITrayService,
ILaunchHookPaths
)
from .exceptions import InvalidContextError
class ExampleTimersManagerConnector:
"""Timers manager can handle timers of multiple modules/addons.
@ -64,7 +70,7 @@ class ExampleTimersManagerConnector:
self._timers_manager_module.timer_stopped(self._module.id)
class TimersManager(OpenPypeModule, ITrayService):
class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths):
""" Handles about Timers.
Should be able to start/stop all timers at once.
@ -151,47 +157,112 @@ class TimersManager(OpenPypeModule, ITrayService):
self._idle_manager.stop()
self._idle_manager.wait()
def start_timer(self, project_name, asset_name, task_name, hierarchy):
"""
Start timer for 'project_name', 'asset_name' and 'task_name'
def get_timer_data_for_path(self, task_path):
"""Convert string path to a timer data.
Called from REST api by hosts.
Args:
project_name (string)
asset_name (string)
task_name (string)
hierarchy (string)
It is expected that first item is project name, last item is task name
and parent asset name is before task name.
"""
path_items = task_path.split("/")
if len(path_items) < 3:
raise InvalidContextError("Invalid path \"{}\"".format(task_path))
task_name = path_items.pop(-1)
asset_name = path_items.pop(-1)
project_name = path_items.pop(0)
return self.get_timer_data_for_context(
project_name, asset_name, task_name, self.log
)
def get_launch_hook_paths(self):
"""Implementation of `ILaunchHookPaths`."""
return os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"launch_hooks"
)
@staticmethod
def get_timer_data_for_context(
project_name, asset_name, task_name, logger=None
):
"""Prepare data for timer related callbacks.
TODO:
- return predefined object that has access to asset document etc.
"""
if not project_name or not asset_name or not task_name:
raise InvalidContextError((
"Missing context information got"
" Project: \"{}\" Asset: \"{}\" Task: \"{}\""
).format(str(project_name), str(asset_name), str(task_name)))
dbconn = AvalonMongoDB()
dbconn.install()
dbconn.Session["AVALON_PROJECT"] = project_name
asset_doc = dbconn.find_one({
"type": "asset", "name": asset_name
})
asset_doc = dbconn.find_one(
{
"type": "asset",
"name": asset_name
},
{
"data.tasks": True,
"data.parents": True
}
)
if not asset_doc:
raise ValueError("Uknown asset {}".format(asset_name))
dbconn.uninstall()
raise InvalidContextError((
"Asset \"{}\" not found in project \"{}\""
).format(asset_name, project_name))
task_type = ''
asset_data = asset_doc.get("data") or {}
asset_tasks = asset_data.get("tasks") or {}
if task_name not in asset_tasks:
dbconn.uninstall()
raise InvalidContextError((
"Task \"{}\" not found on asset \"{}\" in project \"{}\""
).format(task_name, asset_name, project_name))
task_type = ""
try:
task_type = asset_doc["data"]["tasks"][task_name]["type"]
task_type = asset_tasks[task_name]["type"]
except KeyError:
self.log.warning("Couldn't find task_type for {}".
format(task_name))
msg = "Couldn't find task_type for {}".format(task_name)
if logger is not None:
logger.warning(msg)
else:
print(msg)
hierarchy = hierarchy.split("\\")
hierarchy.append(asset_name)
hierarchy_items = asset_data.get("parents") or []
hierarchy_items.append(asset_name)
data = {
dbconn.uninstall()
return {
"project_name": project_name,
"task_name": task_name,
"task_type": task_type,
"hierarchy": hierarchy
"hierarchy": hierarchy_items
}
def start_timer(self, project_name, asset_name, task_name):
"""Start timer for passed context.
Args:
project_name (str): Project name
asset_name (str): Asset name
task_name (str): Task name
"""
data = self.get_timer_data_for_context(
project_name, asset_name, task_name, self.log
)
self.timer_started(None, data)
def get_task_time(self, project_name, asset_name, task_name):
"""Get total time for passed context.
TODO:
- convert context to timer data
"""
times = {}
for module_id, connector in self._connectors_by_module_id.items():
if hasattr(connector, "get_task_time"):
@ -202,6 +273,10 @@ class TimersManager(OpenPypeModule, ITrayService):
return times
def timer_started(self, source_id, data):
"""Connector triggered that timer has started.
New timer has started for context in data.
"""
for module_id, connector in self._connectors_by_module_id.items():
if module_id == source_id:
continue
@ -219,6 +294,14 @@ class TimersManager(OpenPypeModule, ITrayService):
self.is_running = True
def timer_stopped(self, source_id):
"""Connector triggered that hist timer has stopped.
Should stop all other timers.
TODO:
- pass context for which timer has stopped to validate if timers are
same and valid
"""
for module_id, connector in self._connectors_by_module_id.items():
if module_id == source_id:
continue
@ -237,6 +320,7 @@ class TimersManager(OpenPypeModule, ITrayService):
self.timer_started(None, self.last_task)
def stop_timers(self):
"""Stop all timers."""
if self.is_running is False:
return
@ -295,18 +379,40 @@ class TimersManager(OpenPypeModule, ITrayService):
self, server_manager
)
def change_timer_from_host(self, project_name, asset_name, task_name):
"""Prepared method for calling change timers on REST api"""
@staticmethod
def start_timer_with_webserver(
project_name, asset_name, task_name, logger=None
):
"""Prepared method for calling change timers on REST api.
Webserver must be active. At the moment is Webserver running only when
OpenPype Tray is used.
Args:
project_name (str): Project name.
asset_name (str): Asset name.
task_name (str): Task name.
logger (logging.Logger): Logger object. Using 'print' if not
passed.
"""
webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL")
if not webserver_url:
self.log.warning("Couldn't find webserver url")
msg = "Couldn't find webserver url"
if logger is not None:
logger.warning(msg)
else:
print(msg)
return
rest_api_url = "{}/timers_manager/start_timer".format(webserver_url)
try:
import requests
except Exception:
self.log.warning("Couldn't start timer")
msg = "Couldn't start timer ('requests' is not available)"
if logger is not None:
logger.warning(msg)
else:
print(msg)
return
data = {
"project_name": project_name,
@ -314,4 +420,4 @@ class TimersManager(OpenPypeModule, ITrayService):
"task_name": task_name
}
requests.post(rest_api_url, json=data)
return requests.post(rest_api_url, json=data)

View file

@ -359,7 +359,8 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins):
if frame_start is None:
replacement_final = replacement_size = str(MISSING_KEY_VALUE)
else:
replacement_final = "%{eif:n+" + str(frame_start) + ":d}"
replacement_final = "%{eif:n+" + str(frame_start) + ":d:" + \
str(len(str(frame_end))) + "}"
replacement_size = str(frame_end)
final_text = final_text.replace(

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.7.0-nightly.8"
__version__ = "3.7.0-nightly.9"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.7.0-nightly.8" # OpenPype
version = "3.7.0-nightly.9" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"