Merge branch 'pypeclub:develop' into enhancement/minimal-ftrack-browser-open-from-tray

This commit is contained in:
maxpareschi 2022-06-13 15:31:17 +02:00 committed by GitHub
commit b2b52cdcb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 410 additions and 39 deletions

View file

@ -1,23 +1,39 @@
# Changelog
## [3.11.0-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD)
## [3.11.0-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.10.0...HEAD)
### 📖 Documentation
- Documentation: Add app key to template documentation [\#3299](https://github.com/pypeclub/OpenPype/pull/3299)
- doc: adding royal render and multiverse to the web site [\#3285](https://github.com/pypeclub/OpenPype/pull/3285)
**🚀 Enhancements**
- updated poetry installation source [\#3316](https://github.com/pypeclub/OpenPype/pull/3316)
- TVPaint: Extractor use mark in/out range to render [\#3309](https://github.com/pypeclub/OpenPype/pull/3309)
- Ftrack: Delivery action can work on ReviewSessions [\#3307](https://github.com/pypeclub/OpenPype/pull/3307)
- Maya: Look assigner UI improvements [\#3298](https://github.com/pypeclub/OpenPype/pull/3298)
- Ftrack: Action to transfer values of hierarchical attributes [\#3284](https://github.com/pypeclub/OpenPype/pull/3284)
- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269)
- General: Updated windows oiio tool [\#3268](https://github.com/pypeclub/OpenPype/pull/3268)
- Unreal: add support for skeletalMesh and staticMesh to loaders [\#3267](https://github.com/pypeclub/OpenPype/pull/3267)
- Maya: reference loaders could store placeholder in referenced url [\#3264](https://github.com/pypeclub/OpenPype/pull/3264)
- TVPaint: Init file for TVPaint worker also handle guideline images [\#3250](https://github.com/pypeclub/OpenPype/pull/3250)
- Nuke: Change default icon path in settings [\#3247](https://github.com/pypeclub/OpenPype/pull/3247)
- Maya: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225)
- Maya: Look assigner UI improvements [\#3208](https://github.com/pypeclub/OpenPype/pull/3208)
- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186)
**🐛 Bug fixes**
- Houdini: Fix Houdini VDB manage update wrong file attribute name [\#3322](https://github.com/pypeclub/OpenPype/pull/3322)
- General: Vendorized modules for Python 2 and update poetry lock [\#3305](https://github.com/pypeclub/OpenPype/pull/3305)
- Fix - added local targets to install host [\#3303](https://github.com/pypeclub/OpenPype/pull/3303)
- Settings: Add missing default settings for nuke gizmo [\#3301](https://github.com/pypeclub/OpenPype/pull/3301)
- Maya: Fix swaped width and height in reviews [\#3300](https://github.com/pypeclub/OpenPype/pull/3300)
- Maya: point cache publish handles Maya instances [\#3297](https://github.com/pypeclub/OpenPype/pull/3297)
- Global: extract review slate issues [\#3286](https://github.com/pypeclub/OpenPype/pull/3286)
- Webpublisher: return only active projects in ProjectsEndpoint [\#3281](https://github.com/pypeclub/OpenPype/pull/3281)
- Hiero: add support for task tags 3.10.x [\#3279](https://github.com/pypeclub/OpenPype/pull/3279)
@ -30,24 +46,15 @@
- Unreal: Fixed Render creation in UE5 [\#3239](https://github.com/pypeclub/OpenPype/pull/3239)
- Unreal: Fixed Camera loading in UE5 [\#3238](https://github.com/pypeclub/OpenPype/pull/3238)
- Flame: debugging [\#3224](https://github.com/pypeclub/OpenPype/pull/3224)
- add silent audio to slate [\#3162](https://github.com/pypeclub/OpenPype/pull/3162)
**Merged pull requests:**
- Maya: better handling of legacy review subsets names [\#3269](https://github.com/pypeclub/OpenPype/pull/3269)
- Deadline: publishing of animation and pointcache on a farm [\#3225](https://github.com/pypeclub/OpenPype/pull/3225)
- Nuke: add pointcache and animation to loader [\#3186](https://github.com/pypeclub/OpenPype/pull/3186)
- Add a gizmo menu to nuke [\#3172](https://github.com/pypeclub/OpenPype/pull/3172)
- Maya: add pointcache family to gpu cache loader [\#3318](https://github.com/pypeclub/OpenPype/pull/3318)
## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0)
**🆕 New features**
- General: OpenPype modules publish plugins are registered in host [\#3180](https://github.com/pypeclub/OpenPype/pull/3180)
- General: Creator plugins from addons can be registered [\#3179](https://github.com/pypeclub/OpenPype/pull/3179)
**🚀 Enhancements**
- Maya: FBX camera export [\#3253](https://github.com/pypeclub/OpenPype/pull/3253)
@ -55,10 +62,6 @@
- Project Manager: Allow to paste Tasks into multiple assets at the same time [\#3226](https://github.com/pypeclub/OpenPype/pull/3226)
- Project manager: Sped up project load [\#3216](https://github.com/pypeclub/OpenPype/pull/3216)
- Loader UI: Speed issues of loader with sync server [\#3199](https://github.com/pypeclub/OpenPype/pull/3199)
- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190)
- Maya: added clean\_import option to Import loader [\#3181](https://github.com/pypeclub/OpenPype/pull/3181)
- Add the scripts menu definition to nuke [\#3168](https://github.com/pypeclub/OpenPype/pull/3168)
- Maya: add maya 2023 to default applications [\#3167](https://github.com/pypeclub/OpenPype/pull/3167)
**🐛 Bug fixes**
@ -76,12 +79,6 @@
- Photoshop: skip collector when automatic testing [\#3202](https://github.com/pypeclub/OpenPype/pull/3202)
- Nuke: render/workfile version sync doesn't work on farm [\#3185](https://github.com/pypeclub/OpenPype/pull/3185)
- Ftrack: Review image only if there are no mp4 reviews [\#3183](https://github.com/pypeclub/OpenPype/pull/3183)
- Ftrack: Locations deepcopy issue [\#3177](https://github.com/pypeclub/OpenPype/pull/3177)
- General: Avoid creating multiple thumbnails [\#3176](https://github.com/pypeclub/OpenPype/pull/3176)
- General/Hiero: better clip duration calculation [\#3169](https://github.com/pypeclub/OpenPype/pull/3169)
- General: Oiio conversion for ffmpeg checks for invalid characters [\#3166](https://github.com/pypeclub/OpenPype/pull/3166)
- Fix for attaching render to subset [\#3164](https://github.com/pypeclub/OpenPype/pull/3164)
- Harmony: fixed missing task name in render instance [\#3163](https://github.com/pypeclub/OpenPype/pull/3163)
**🔀 Refactored code**
@ -92,7 +89,6 @@
- Harmony: message length in 21.1 [\#3257](https://github.com/pypeclub/OpenPype/pull/3257)
- Harmony: 21.1 fix [\#3249](https://github.com/pypeclub/OpenPype/pull/3249)
- Maya: added jpg to filter for Image Plane Loader [\#3223](https://github.com/pypeclub/OpenPype/pull/3223)
- Webpublisher: replace space by underscore in subset names [\#3160](https://github.com/pypeclub/OpenPype/pull/3160)
## [3.9.8](https://github.com/pypeclub/OpenPype/tree/3.9.8) (2022-05-19)
@ -103,15 +99,13 @@
- nuke: generate publishing nodes inside render group node [\#3206](https://github.com/pypeclub/OpenPype/pull/3206)
- Loader UI: Speed issues of loader with sync server [\#3200](https://github.com/pypeclub/OpenPype/pull/3200)
- Backport of fix for attaching renders to subsets [\#3195](https://github.com/pypeclub/OpenPype/pull/3195)
- Looks: add basic support for Renderman [\#3190](https://github.com/pypeclub/OpenPype/pull/3190)
**🐛 Bug fixes**
- Standalone Publisher: Always create new representation for thumbnail [\#3204](https://github.com/pypeclub/OpenPype/pull/3204)
- Nuke: render/workfile version sync doesn't work on farm [\#3184](https://github.com/pypeclub/OpenPype/pull/3184)
- Ftrack: Review image only if there are no mp4 reviews [\#3182](https://github.com/pypeclub/OpenPype/pull/3182)
- Ftrack: Locations deepcopy issue [\#3175](https://github.com/pypeclub/OpenPype/pull/3175)
- General: Avoid creating multiple thumbnails [\#3174](https://github.com/pypeclub/OpenPype/pull/3174)
- General: TemplateResult can be copied [\#3170](https://github.com/pypeclub/OpenPype/pull/3170)
**Merged pull requests:**

View file

@ -132,7 +132,7 @@ def create_time_effects(otio_clip, track_item):
otio_effect = otio.schema.TimeEffect()
otio_effect.name = name
otio_effect.effect_name = effect_name
otio_effect.metadata = metadata
otio_effect.metadata.update(metadata)
# add otio effect to clip effects
otio_clip.effects.append(otio_effect)

View file

@ -10,7 +10,7 @@ from openpype.api import get_project_settings
class GpuCacheLoader(load.LoaderPlugin):
"""Load Alembic as gpuCache"""
families = ["model"]
families = ["model", "animation", "pointcache"]
representations = ["abc"]
label = "Import Gpu Cache"

View file

@ -1,9 +1,49 @@
from maya import cmds
import maya.api.OpenMaya as om
import pyblish.api
import json
def get_all_children(nodes):
"""Return all children of `nodes` including each instanced child.
Using maya.cmds.listRelatives(allDescendents=True) includes only the first
instance. As such, this function acts as an optimal replacement with a
focus on a fast query.
"""
sel = om.MSelectionList()
traversed = set()
iterator = om.MItDag(om.MItDag.kDepthFirst)
for node in nodes:
if node in traversed:
# Ignore if already processed as a child
# before
continue
sel.clear()
sel.add(node)
dag = sel.getDagPath(0)
iterator.reset(dag)
next(iterator) # ignore self
while not iterator.isDone():
path = iterator.fullPathName()
if path in traversed:
iterator.prune()
next(iterator)
continue
traversed.add(path)
next(iterator)
return list(traversed)
class CollectInstances(pyblish.api.ContextPlugin):
"""Gather instances by objectSet and pre-defined attribute
@ -86,12 +126,8 @@ class CollectInstances(pyblish.api.ContextPlugin):
# Collect members
members = cmds.ls(members, long=True) or []
# `maya.cmds.listRelatives(noIntermediate=True)` only works when
# `shapes=True` argument is passed, since we also want to include
# transforms we filter afterwards.
children = cmds.listRelatives(members,
allDescendents=True,
fullPath=True) or []
dag_members = cmds.ls(members, type="dagNode", long=True)
children = get_all_children(dag_members)
children = cmds.ls(children, noIntermediate=True, long=True)
parents = []

View file

@ -0,0 +1,288 @@
import threading
import datetime
import copy
import collections
import ftrack_api
from openpype.lib import get_datetime_data
from openpype.api import get_project_settings
from openpype_modules.ftrack.lib import ServerAction
class CreateDailyReviewSessionServerAction(ServerAction):
"""Create daily review session object per project.
Action creates review sessions based on settings. Settings define if is
action enabled and what is a template for review session name. Logic works
in a way that if review session with the name already exists then skip
process. If review session for current day does not exist but yesterdays
review exists and is empty then yesterdays is renamed otherwise creates
new review session.
Also contains cycle creation of dailies which is triggered each morning.
This option must be enabled in project settings. Cycle creation is also
checked on registration of action.
"""
identifier = "create.daily.review.session"
#: Action label.
label = "OpenPype Admin"
variant = "- Create Daily Review Session (Server)"
#: Action description.
description = "Manually create daily review session"
role_list = {"Pypeclub", "Administrator", "Project Manager"}
settings_key = "create_daily_review_session"
default_template = "{yy}{mm}{dd}"
def __init__(self, *args, **kwargs):
super(CreateDailyReviewSessionServerAction, self).__init__(
*args, **kwargs
)
self._cycle_timer = None
self._last_cyle_time = None
self._day_delta = datetime.timedelta(days=1)
def discover(self, session, entities, event):
"""Show action only on AssetVersions."""
valid_selection = False
for ent in event["data"]["selection"]:
# Ignore entities that are not tasks or projects
if ent["entityType"].lower() in (
"show", "task", "reviewsession", "assetversion"
):
valid_selection = True
break
if not valid_selection:
return False
return self.valid_roles(session, entities, event)
def launch(self, session, entities, event):
project_entity = self.get_project_from_entity(entities[0], session)
project_name = project_entity["full_name"]
project_settings = self.get_project_settings_from_event(
event, project_name
)
action_settings = self._extract_action_settings(project_settings)
project_name_by_id = {
project_entity["id"]: project_name
}
settings_by_project_id = {
project_entity["id"]: action_settings
}
self._process_review_session(
session, settings_by_project_id, project_name_by_id
)
return True
def register(self, *args, **kwargs):
"""Override register to be able trigger """
# Register server action as would be normally
super(CreateDailyReviewSessionServerAction, self).register(
*args, **kwargs
)
# Create threading timer which will trigger creation of report
# at the 00:00:01 of next day
# - callback will trigger another timer which will have 1 day offset
now = datetime.datetime.now()
# Create object of today morning
today_morning = datetime.datetime(
now.year, now.month, now.day, 0, 0, 1
)
# Add a day delta (to calculate next day date)
next_day_morning = today_morning + self._day_delta
# Calculate first delta in seconds for first threading timer
first_delta = (next_day_morning - now).total_seconds()
# Store cycle time which will be used to create next timer
self._last_cyle_time = next_day_morning
# Create timer thread
self._cycle_timer = threading.Timer(first_delta, self._timer_callback)
self._cycle_timer.start()
self._check_review_session()
def _timer_callback(self):
if (
self._cycle_timer is not None
and self._last_cyle_time is not None
):
now = datetime.datetime.now()
while self._last_cyle_time < now:
self._last_cyle_time = self._last_cyle_time + self._day_delta
delay = (self._last_cyle_time - now).total_seconds()
self._cycle_timer = threading.Timer(delay, self._timer_callback)
self._cycle_timer.start()
self._check_review_session()
def _check_review_session(self):
session = ftrack_api.Session(
server_url=self.session.server_url,
api_key=self.session.api_key,
api_user=self.session.api_user,
auto_connect_event_hub=False
)
project_entities = session.query(
"select id, full_name from Project"
).all()
project_names_by_id = {
project_entity["id"]: project_entity["full_name"]
for project_entity in project_entities
}
action_settings_by_project_id = self._get_action_settings(
project_names_by_id
)
enabled_action_settings_by_project_id = {}
for item in action_settings_by_project_id.items():
project_id, action_settings = item
if action_settings.get("cycle_enabled"):
enabled_action_settings_by_project_id[project_id] = (
action_settings
)
if not enabled_action_settings_by_project_id:
self.log.info((
"There are no projects that have enabled"
" cycle review sesison creation"
))
else:
self._process_review_session(
session,
enabled_action_settings_by_project_id,
project_names_by_id
)
session.close()
def _process_review_session(
self, session, settings_by_project_id, project_names_by_id
):
review_sessions = session.query((
"select id, name, project_id"
" from ReviewSession where project_id in ({})"
).format(self.join_query_keys(settings_by_project_id))).all()
review_sessions_by_project_id = collections.defaultdict(list)
for review_session in review_sessions:
project_id = review_session["project_id"]
review_sessions_by_project_id[project_id].append(review_session)
# Prepare fill data for today's review sesison and yesterdays
now = datetime.datetime.now()
today_obj = datetime.datetime(
now.year, now.month, now.day, 0, 0, 0
)
yesterday_obj = today_obj - self._day_delta
today_fill_data = get_datetime_data(today_obj)
yesterday_fill_data = get_datetime_data(yesterday_obj)
# Loop through projects and try to create daily reviews
for project_id, action_settings in settings_by_project_id.items():
review_session_template = (
action_settings["review_session_template"]
).strip() or self.default_template
today_project_fill_data = copy.deepcopy(today_fill_data)
yesterday_project_fill_data = copy.deepcopy(yesterday_fill_data)
project_name = project_names_by_id[project_id]
today_project_fill_data["project_name"] = project_name
yesterday_project_fill_data["project_name"] = project_name
today_session_name = self._fill_review_template(
review_session_template, today_project_fill_data
)
yesterday_session_name = self._fill_review_template(
review_session_template, yesterday_project_fill_data
)
# Skip if today's session name could not be filled
if not today_session_name:
continue
# Find matchin review session
project_review_sessions = review_sessions_by_project_id[project_id]
todays_session = None
yesterdays_session = None
for review_session in project_review_sessions:
session_name = review_session["name"]
if session_name == today_session_name:
todays_session = review_session
break
elif session_name == yesterday_session_name:
yesterdays_session = review_session
# Skip if today's session already exist
if todays_session is not None:
self.log.debug((
"Todays ReviewSession \"{}\""
" in project \"{}\" already exists"
).format(today_session_name, project_name))
continue
# Check if there is yesterday's session and is empty
# - in that case just rename it
if (
yesterdays_session is not None
and len(yesterdays_session["review_session_objects"]) == 0
):
self.log.debug((
"Renaming yesterdays empty review session \"{}\" to \"{}\""
" in project \"{}\""
).format(
yesterday_session_name, today_session_name, project_name
))
yesterdays_session["name"] = today_session_name
session.commit()
continue
# Create new review session with new name
self.log.debug((
"Creating new review session \"{}\" in project \"{}\""
).format(today_session_name, project_name))
session.create("ReviewSession", {
"project_id": project_id,
"name": today_session_name
})
session.commit()
def _get_action_settings(self, project_names_by_id):
settings_by_project_id = {}
for project_id, project_name in project_names_by_id.items():
project_settings = get_project_settings(project_name)
action_settings = self._extract_action_settings(project_settings)
settings_by_project_id[project_id] = action_settings
return settings_by_project_id
def _extract_action_settings(self, project_settings):
return (
project_settings
.get("ftrack", {})
.get(self.settings_frack_subkey, {})
.get(self.settings_key)
) or {}
def _fill_review_template(self, template, data):
output = None
try:
output = template.format(**data)
except Exception:
self.log.warning(
(
"Failed to fill review session template {} with data {}"
).format(template, data),
exc_info=True
)
return output
def register(session):
'''Register plugin. Called when used as an plugin.'''
CreateDailyReviewSessionServerAction(session).register()

View file

@ -116,6 +116,15 @@
"Administrator",
"Project manager"
]
},
"create_daily_review_session": {
"enabled": true,
"role_list": [
"Administrator",
"Project Manager"
],
"cycle_enabled": false,
"review_session_template": "{yy}{mm}{dd}"
}
},
"user_handlers": {

View file

@ -388,6 +388,44 @@
"object_type": "text"
}
]
},
{
"key": "create_daily_review_session",
"label": "Create daily review session",
"type": "dict",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text",
"use_label_wrap": true
},
{
"type": "boolean",
"key": "cycle_enabled",
"label": "Create daily review session"
},
{
"type": "separator"
},
{
"type": "text",
"key": "review_session_template",
"label": "ReviewSession template",
"placeholder": "Default: {yy}{mm}{dd}"
},
{
"type": "label",
"label": "Possible formatting keys in template:<br/>- \"project_name\" - &lt;Name of project&gt;<br/>- \"d\" - &lt;Day of month number&gt; in shortest possible way.<br/>- \"dd\" - &lt;Day of month number&gt; with 2 digits.<br/>- \"ddd\" - &lt;Week day name&gt; shortened week day. e.g.: `Mon`, ...<br/>- \"dddd\" - &lt;Week day name&gt; full name of week day. e.g.: `Monday`, ...<br/>- \"m\" - &lt;Month number&gt; in shortest possible way. e.g.: `1` if January<br/>- \"mm\" - &lt;Month number&gt; with 2 digits.<br/>- \"mmm\" - &lt;Month name&gt; shortened month name. e.g.: `Jan`, ...<br/>- \"mmmm\" -&lt;Month name&gt; full month name. e.g.: `January`, ...<br/>- \"yy\" - &lt;Year number&gt; shortened year. e.g.: `19`, `20`, ...<br/>- \"yyyy\" - &lt;Year number&gt; full year. e.g.: `2019`, `2020`, ..."
}
]
}
]
},

View file

@ -205,3 +205,9 @@ class ToolsDelegate(QtWidgets.QStyledItemDelegate):
def setModelData(self, editor, model, index):
model.setData(index, editor.value(), QtCore.Qt.EditRole)
def displayText(self, value, locale):
if value:
return ", ".join(value)
else:
return

View file

@ -381,7 +381,7 @@ class HierarchyView(QtWidgets.QTreeView):
self._source_model.delete_indexes(indexes)
def _on_ctrl_shift_enter_pressed(self):
self._add_task_and_edit()
self.add_task_and_edit()
def add_asset(self, parent_index=None):
if parent_index is None:
@ -423,9 +423,9 @@ class HierarchyView(QtWidgets.QTreeView):
self.edit(new_index)
def _add_task_action(self):
self._add_task_and_edit()
self.add_task_and_edit()
def _add_task_and_edit(self):
def add_task_and_edit(self):
new_index = self.add_task()
if new_index is None:
return

View file

@ -245,7 +245,7 @@ class ProjectManagerWindow(QtWidgets.QWidget):
self.hierarchy_view.add_asset()
def _on_add_task(self):
self.hierarchy_view.add_task()
self.hierarchy_view.add_task_and_edit()
def _on_create_folders(self):
project_name = self._current_project()

View file

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

View file

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