mirror of
https://github.com/ynput/ayon-core.git
synced 2026-01-01 16:34:53 +01:00
Merge branch 'develop' into feature/PYPE-14-ftrack-stop-timer
This commit is contained in:
commit
c7f4476545
16 changed files with 636 additions and 202 deletions
|
|
@ -219,7 +219,7 @@ class SyncToAvalon(BaseAction):
|
||||||
})
|
})
|
||||||
|
|
||||||
elif self.avalon_project['name'] != entity['full_name']:
|
elif self.avalon_project['name'] != entity['full_name']:
|
||||||
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(avalon_asset['name'], name))
|
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(self.avalon_project['name'], name))
|
||||||
|
|
||||||
data = ftrack_utils.get_data(self, entity, session,self.custom_attributes)
|
data = ftrack_utils.get_data(self, entity, session,self.custom_attributes)
|
||||||
|
|
||||||
|
|
@ -235,7 +235,7 @@ class SyncToAvalon(BaseAction):
|
||||||
if self.ca_mongoid in entity['custom_attributes']:
|
if self.ca_mongoid in entity['custom_attributes']:
|
||||||
entity['custom_attributes'][self.ca_mongoid] = str(self.projectId)
|
entity['custom_attributes'][self.ca_mongoid] = str(self.projectId)
|
||||||
else:
|
else:
|
||||||
self.log.error("Custom attribute for <{}> is not created.".format(entity['name']))
|
self.log.error('Custom attribute for "{}" is not created.'.format(entity['name']))
|
||||||
return
|
return
|
||||||
|
|
||||||
## ----- ASSETS ------
|
## ----- ASSETS ------
|
||||||
|
|
@ -271,15 +271,12 @@ class SyncToAvalon(BaseAction):
|
||||||
self.log.debug("Asset {} - created".format(name))
|
self.log.debug("Asset {} - created".format(name))
|
||||||
|
|
||||||
# Raise error if it seems to be different ent. with same name
|
# Raise error if it seems to be different ent. with same name
|
||||||
else:
|
elif (avalon_asset['data']['parents'] != data['parents'] or
|
||||||
aD = avalon_asset['data']
|
avalon_asset['silo'] != silo):
|
||||||
# check_attr = ['parents', 'ftrackId', 'visualParent']
|
|
||||||
if (avalon_asset['data']['parents'] != data['parents'] or
|
|
||||||
avalon_asset['silo'] != silo):
|
|
||||||
raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name))
|
raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name))
|
||||||
|
|
||||||
elif avalon_asset['name'] != entity['name']:
|
elif avalon_asset['name'] != entity['name']:
|
||||||
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please create new asset'.format(avalon_asset['name'], name))
|
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please set name back'.format(avalon_asset['name'], name))
|
||||||
elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']:
|
elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']:
|
||||||
old_path = "/".join(avalon_asset['data']['parents'])
|
old_path = "/".join(avalon_asset['data']['parents'])
|
||||||
new_path = "/".join(data['parents'])
|
new_path = "/".join(data['parents'])
|
||||||
|
|
|
||||||
225
pype/ftrack/events/event_sync_to_avalon.py
Normal file
225
pype/ftrack/events/event_sync_to_avalon.py
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import ftrack_api
|
||||||
|
from ftrack_event_handler import BaseEvent
|
||||||
|
from pype import lib
|
||||||
|
from avalon import io, inventory
|
||||||
|
from avalon.vendor import toml
|
||||||
|
from bson.objectid import ObjectId
|
||||||
|
from pype.ftrack import ftrack_utils
|
||||||
|
|
||||||
|
class Sync_to_Avalon(BaseEvent):
|
||||||
|
|
||||||
|
def launch(self, session, entities, event):
|
||||||
|
|
||||||
|
self.ca_mongoid = 'avalon_mongo_id'
|
||||||
|
# If mongo_id textfield has changed: RETURN!
|
||||||
|
# - infinite loop
|
||||||
|
for ent in event['data']['entities']:
|
||||||
|
if 'keys' in ent:
|
||||||
|
if self.ca_mongoid in ent['keys']:
|
||||||
|
return
|
||||||
|
self.proj = None
|
||||||
|
|
||||||
|
# get project
|
||||||
|
for entity in entities:
|
||||||
|
try:
|
||||||
|
base_proj = entity['link'][0]
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
self.proj = session.get(base_proj['type'], base_proj['id'])
|
||||||
|
break
|
||||||
|
|
||||||
|
# check if project is set to auto-sync
|
||||||
|
if (self.proj is None or
|
||||||
|
'avalon_auto_sync' not in self.proj['custom_attributes'] or
|
||||||
|
self.proj['custom_attributes']['avalon_auto_sync'] is False):
|
||||||
|
return
|
||||||
|
|
||||||
|
# check if project have Custom Attribute 'avalon_mongo_id'
|
||||||
|
if self.ca_mongoid not in self.proj['custom_attributes']:
|
||||||
|
message = "Custom attribute '{}' for 'Project' is not created or don't have set permissions for API".format(self.ca_mongoid)
|
||||||
|
self.log.warning(message)
|
||||||
|
self.show_message(event, message, False)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.projectId = self.proj['custom_attributes'][self.ca_mongoid]
|
||||||
|
|
||||||
|
os.environ["AVALON_PROJECT"] = self.proj['full_name']
|
||||||
|
|
||||||
|
# get avalon project if possible
|
||||||
|
io.install()
|
||||||
|
try:
|
||||||
|
self.avalon_project = io.find_one({"_id": ObjectId(self.projectId)})
|
||||||
|
except:
|
||||||
|
self.avalon_project = None
|
||||||
|
|
||||||
|
importEntities = []
|
||||||
|
if self.avalon_project is None:
|
||||||
|
self.avalon_project = io.find_one({"type": "project", "name": self.proj["full_name"]})
|
||||||
|
if self.avalon_project is None:
|
||||||
|
importEntities.append(self.proj)
|
||||||
|
else:
|
||||||
|
self.projectId = self.avalon_project['_id']
|
||||||
|
|
||||||
|
io.uninstall()
|
||||||
|
|
||||||
|
for entity in entities:
|
||||||
|
if entity.entity_type.lower() in ['task']:
|
||||||
|
entity = entity['parent']
|
||||||
|
|
||||||
|
try:
|
||||||
|
mongo_id = entity['custom_attributes'][self.ca_mongoid]
|
||||||
|
except:
|
||||||
|
message = "Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity.entity_type)
|
||||||
|
self.log.warning(message)
|
||||||
|
self.show_message(event, message, False)
|
||||||
|
return
|
||||||
|
|
||||||
|
if entity not in importEntities:
|
||||||
|
importEntities.append(entity)
|
||||||
|
|
||||||
|
if len(importEntities) < 1:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.setAvalonAttributes()
|
||||||
|
|
||||||
|
io.install()
|
||||||
|
try:
|
||||||
|
for entity in importEntities:
|
||||||
|
self.importToAvalon(session, entity)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
except ValueError as ve:
|
||||||
|
message = str(ve)
|
||||||
|
self.show_message(event, message, False)
|
||||||
|
self.log.warning(message)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
message = str(e)
|
||||||
|
ftrack_message = "SyncToAvalon event ended with unexpected error please check log file for more information."
|
||||||
|
self.show_message(event, ftrack_message, False)
|
||||||
|
self.log.error(message)
|
||||||
|
|
||||||
|
io.uninstall()
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def importToAvalon(self, session, entity):
|
||||||
|
if self.ca_mongoid not in entity['custom_attributes']:
|
||||||
|
raise ValueError("Custom attribute '{}' for '{}' is not created or don't have set permissions for API".format(self.ca_mongoid, entity['name']))
|
||||||
|
|
||||||
|
ftrack_utils.avalon_check_name(entity)
|
||||||
|
|
||||||
|
entity_type = entity.entity_type
|
||||||
|
|
||||||
|
if entity_type in ['Project']:
|
||||||
|
type = 'project'
|
||||||
|
name = entity['full_name']
|
||||||
|
config = ftrack_utils.get_config(entity)
|
||||||
|
template = lib.get_avalon_project_template_schema()
|
||||||
|
|
||||||
|
if self.avalon_project is None:
|
||||||
|
inventory.save(name, config, template)
|
||||||
|
self.avalon_project = io.find_one({'type': 'project', 'name': name})
|
||||||
|
|
||||||
|
elif self.avalon_project['name'] != name:
|
||||||
|
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly!'.format(self.avalon_project['name'], name))
|
||||||
|
|
||||||
|
self.projectId = self.avalon_project['_id']
|
||||||
|
|
||||||
|
data = ftrack_utils.get_data(self, entity, session,self.custom_attributes)
|
||||||
|
|
||||||
|
io.update_many(
|
||||||
|
{"_id": ObjectId(self.projectId)},
|
||||||
|
{'$set':{
|
||||||
|
'name':name,
|
||||||
|
'config':config,
|
||||||
|
'data':data,
|
||||||
|
}})
|
||||||
|
|
||||||
|
entity['custom_attributes'][self.ca_mongoid] = str(self.projectId)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.avalon_project is None:
|
||||||
|
self.importToAvalon(session, self.proj)
|
||||||
|
|
||||||
|
data = ftrack_utils.get_data(self, entity, session,self.custom_attributes)
|
||||||
|
|
||||||
|
# return if entity is silo
|
||||||
|
if len(data['parents']) == 0:
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
silo = data['parents'][0]
|
||||||
|
|
||||||
|
name = entity['name']
|
||||||
|
|
||||||
|
os.environ["AVALON_ASSET"] = name
|
||||||
|
os.environ['AVALON_SILO'] = silo
|
||||||
|
|
||||||
|
avalon_asset = None
|
||||||
|
# existence of this custom attr is already checked
|
||||||
|
mongo_id = entity['custom_attributes'][self.ca_mongoid]
|
||||||
|
|
||||||
|
if mongo_id is not "":
|
||||||
|
avalon_asset = io.find_one({'_id': ObjectId(mongo_id)})
|
||||||
|
|
||||||
|
if avalon_asset is None:
|
||||||
|
avalon_asset = io.find_one({'type': 'asset', 'name': name})
|
||||||
|
if avalon_asset is None:
|
||||||
|
mongo_id = inventory.create_asset(name, silo, data, ObjectId(self.projectId))
|
||||||
|
# Raise error if it seems to be different ent. with same name
|
||||||
|
elif (avalon_asset['data']['parents'] != data['parents'] or
|
||||||
|
avalon_asset['silo'] != silo):
|
||||||
|
raise ValueError('In Avalon DB already exists entity with name "{0}"'.format(name))
|
||||||
|
elif avalon_asset['name'] != entity['name']:
|
||||||
|
raise ValueError('You can\'t change name {} to {}, avalon DB won\'t work properly - please set name back'.format(avalon_asset['name'], name))
|
||||||
|
elif avalon_asset['silo'] != silo or avalon_asset['data']['parents'] != data['parents']:
|
||||||
|
old_path = "/".join(avalon_asset['data']['parents'])
|
||||||
|
new_path = "/".join(data['parents'])
|
||||||
|
raise ValueError('You can\'t move with entities. Entity "{}" was moved from "{}" to "{}" , avalon DB won\'t work properly'.format(avalon_asset['name'], old_path, new_path))
|
||||||
|
|
||||||
|
|
||||||
|
io.update_many(
|
||||||
|
{"_id": ObjectId(mongo_id)},
|
||||||
|
{'$set':{
|
||||||
|
'name':name,
|
||||||
|
'silo':silo,
|
||||||
|
'data':data,
|
||||||
|
'parent': ObjectId(self.projectId)}})
|
||||||
|
|
||||||
|
entity['custom_attributes'][self.ca_mongoid] = str(mongo_id)
|
||||||
|
|
||||||
|
def setAvalonAttributes(self):
|
||||||
|
self.custom_attributes = []
|
||||||
|
all_avalon_attr = self.session.query('CustomAttributeGroup where name is "avalon"').one()
|
||||||
|
for cust_attr in all_avalon_attr['custom_attribute_configurations']:
|
||||||
|
if 'avalon_' not in cust_attr['key']:
|
||||||
|
self.custom_attributes.append(cust_attr)
|
||||||
|
|
||||||
|
def _translate_event(self, session, event):
|
||||||
|
exceptions = ['assetversion', 'job', 'user', 'reviewsessionobject', 'timer', 'socialfeed', 'timelog']
|
||||||
|
_selection = event['data'].get('entities',[])
|
||||||
|
|
||||||
|
_entities = list()
|
||||||
|
for entity in _selection:
|
||||||
|
if entity['entityType'] in exceptions:
|
||||||
|
continue
|
||||||
|
_entities.append(
|
||||||
|
(
|
||||||
|
session.get(self._get_entity_type(entity), entity.get('entityId'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [_entities, event]
|
||||||
|
|
||||||
|
def register(session, **kw):
|
||||||
|
'''Register plugin. Called when used as an plugin.'''
|
||||||
|
|
||||||
|
if not isinstance(session, ftrack_api.session.Session):
|
||||||
|
return
|
||||||
|
|
||||||
|
event = Sync_to_Avalon(session)
|
||||||
|
event.register()
|
||||||
25
pype/ftrack/events/event_test.py
Normal file
25
pype/ftrack/events/event_test.py
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import ftrack_api
|
||||||
|
from ftrack_event_handler import BaseEvent
|
||||||
|
from app import api
|
||||||
|
|
||||||
|
class Test_Event(BaseEvent):
|
||||||
|
|
||||||
|
def launch(self, session, entities, event):
|
||||||
|
|
||||||
|
'''just a testing event'''
|
||||||
|
|
||||||
|
# self.log.info(event)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def register(session, **kw):
|
||||||
|
'''Register plugin. Called when used as an plugin.'''
|
||||||
|
if not isinstance(session, ftrack_api.session.Session):
|
||||||
|
return
|
||||||
|
|
||||||
|
event = Test_Event(session)
|
||||||
|
event.register()
|
||||||
154
pype/ftrack/events/ftrack_event_handler.py
Normal file
154
pype/ftrack/events/ftrack_event_handler.py
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
# :coding: utf-8
|
||||||
|
# :copyright: Copyright (c) 2017 ftrack
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import getpass
|
||||||
|
# import platform
|
||||||
|
import ftrack_api
|
||||||
|
import toml
|
||||||
|
from avalon import io, lib, pipeline
|
||||||
|
from avalon import session as sess
|
||||||
|
import acre
|
||||||
|
|
||||||
|
from app.api import (
|
||||||
|
Templates,
|
||||||
|
Logger
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseEvent(object):
|
||||||
|
'''Custom Event base class
|
||||||
|
|
||||||
|
BaseEvent is based on ftrack.update event
|
||||||
|
- get entities from event
|
||||||
|
|
||||||
|
If want to use different event base
|
||||||
|
- override register and *optional _translate_event method
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, session):
|
||||||
|
'''Expects a ftrack_api.Session instance'''
|
||||||
|
|
||||||
|
self.log = Logger.getLogger(self.__class__.__name__)
|
||||||
|
|
||||||
|
self._session = session
|
||||||
|
|
||||||
|
@property
|
||||||
|
def session(self):
|
||||||
|
'''Return current session.'''
|
||||||
|
return self._session
|
||||||
|
|
||||||
|
def register(self):
|
||||||
|
'''Registers the event, subscribing the the discover and launch topics.'''
|
||||||
|
self.session.event_hub.subscribe('topic=ftrack.update', self._launch)
|
||||||
|
|
||||||
|
self.log.info("Event '{}' - Registered successfully".format(self.__class__.__name__))
|
||||||
|
|
||||||
|
def _translate_event(self, session, event):
|
||||||
|
'''Return *event* translated structure to be used with the API.'''
|
||||||
|
_selection = event['data'].get('entities',[])
|
||||||
|
|
||||||
|
_entities = list()
|
||||||
|
for entity in _selection:
|
||||||
|
if entity['entityType'] in ['socialfeed']:
|
||||||
|
continue
|
||||||
|
_entities.append(
|
||||||
|
(
|
||||||
|
session.get(self._get_entity_type(entity), entity.get('entityId'))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return [
|
||||||
|
_entities,
|
||||||
|
event
|
||||||
|
]
|
||||||
|
|
||||||
|
def _get_entity_type(self, entity):
|
||||||
|
'''Return translated entity type tht can be used with API.'''
|
||||||
|
# Get entity type and make sure it is lower cased. Most places except
|
||||||
|
# the component tab in the Sidebar will use lower case notation.
|
||||||
|
entity_type = entity.get('entityType').replace('_', '').lower()
|
||||||
|
|
||||||
|
for schema in self.session.schemas:
|
||||||
|
alias_for = schema.get('alias_for')
|
||||||
|
|
||||||
|
if (
|
||||||
|
alias_for and isinstance(alias_for, str) and
|
||||||
|
alias_for.lower() == entity_type
|
||||||
|
):
|
||||||
|
return schema['id']
|
||||||
|
|
||||||
|
for schema in self.session.schemas:
|
||||||
|
if schema['id'].lower() == entity_type:
|
||||||
|
return schema['id']
|
||||||
|
|
||||||
|
raise ValueError(
|
||||||
|
'Unable to translate entity type: {0}.'.format(entity_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
def _launch(self, event):
|
||||||
|
|
||||||
|
self.session.reset()
|
||||||
|
|
||||||
|
args = self._translate_event(
|
||||||
|
self.session, event
|
||||||
|
)
|
||||||
|
|
||||||
|
self.launch(
|
||||||
|
self.session, *args
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def launch(self, session, entities, event):
|
||||||
|
'''Callback method for the custom action.
|
||||||
|
|
||||||
|
return either a bool ( True if successful or False if the action failed )
|
||||||
|
or a dictionary with they keys `message` and `success`, the message should be a
|
||||||
|
string and will be displayed as feedback to the user, success should be a bool,
|
||||||
|
True if successful or False if the action failed.
|
||||||
|
|
||||||
|
*session* is a `ftrack_api.Session` instance
|
||||||
|
|
||||||
|
*entities* is a list of tuples each containing the entity type and the entity id.
|
||||||
|
If the entity is a hierarchical you will always get the entity
|
||||||
|
type TypedContext, once retrieved through a get operation you
|
||||||
|
will have the "real" entity type ie. example Shot, Sequence
|
||||||
|
or Asset Build.
|
||||||
|
|
||||||
|
*event* the unmodified original event
|
||||||
|
|
||||||
|
'''
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def show_message(self, event, input_message, result = False):
|
||||||
|
"""
|
||||||
|
Shows message to user who triggered event
|
||||||
|
- event - just source of user id
|
||||||
|
- input_message - message that is shown to user
|
||||||
|
- result - changes color of message (based on ftrack settings)
|
||||||
|
- True = Violet
|
||||||
|
- False = Red
|
||||||
|
"""
|
||||||
|
if not isinstance(result, bool):
|
||||||
|
result = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = str(input_message)
|
||||||
|
except:
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = event['source']['user']['id']
|
||||||
|
self.session.event_hub.publish(
|
||||||
|
ftrack_api.event.base.Event(
|
||||||
|
topic='ftrack.action.trigger-user-interface',
|
||||||
|
data=dict(
|
||||||
|
type='message',
|
||||||
|
success=result,
|
||||||
|
message=message
|
||||||
|
),
|
||||||
|
target='applicationId=ftrack.client.web and user.id="{0}"'.format(user_id)
|
||||||
|
),
|
||||||
|
on_error='ignore'
|
||||||
|
)
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
# import ftrack_api as local session
|
|
||||||
import ftrack_api
|
|
||||||
from utils import print_entity_head
|
|
||||||
#
|
|
||||||
session = ftrack_api.Session()
|
|
||||||
|
|
||||||
# ----------------------------------
|
|
||||||
|
|
||||||
|
|
||||||
def test_event(event):
|
|
||||||
'''just a testing event'''
|
|
||||||
|
|
||||||
# start of event procedure ----------------------------------
|
|
||||||
for entity in event['data'].get('entities', []):
|
|
||||||
if entity['entityType'] == 'task' and entity['action'] == 'update':
|
|
||||||
|
|
||||||
print "\n\nevent script: {}".format(__file__)
|
|
||||||
print_entity_head.print_entity_head(entity, session)
|
|
||||||
|
|
||||||
# for k in task.keys():
|
|
||||||
# print k, task[k]
|
|
||||||
# print '\n'
|
|
||||||
# print task['assignments']
|
|
||||||
|
|
||||||
for e in entity.keys():
|
|
||||||
print '{0}: {1}'.format(e, entity[e])
|
|
||||||
|
|
||||||
# end of event procedure ----------------------------------
|
|
||||||
|
|
@ -62,7 +62,7 @@ def get_data(parent, entity, session, custom_attributes):
|
||||||
for parent in parents:
|
for parent in parents:
|
||||||
parentId = io.find_one({'type': 'asset', 'name': parName})['_id']
|
parentId = io.find_one({'type': 'asset', 'name': parName})['_id']
|
||||||
if parent['parent'].entity_type != 'project' and parentId is None:
|
if parent['parent'].entity_type != 'project' and parentId is None:
|
||||||
parent.importToAvalon(parent)
|
parent.importToAvalon(session, parent)
|
||||||
parentId = io.find_one({'type': 'asset', 'name': parName})['_id']
|
parentId = io.find_one({'type': 'asset', 'name': parName})['_id']
|
||||||
|
|
||||||
hierarchy = os.path.sep.join(folderStruct)
|
hierarchy = os.path.sep.join(folderStruct)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import sys
|
||||||
from avalon import api as avalon
|
from avalon import api as avalon
|
||||||
from pyblish import api as pyblish
|
from pyblish import api as pyblish
|
||||||
|
|
||||||
from .. import api as pype
|
from .. import api
|
||||||
|
|
||||||
from pype.nuke import menu
|
from pype.nuke import menu
|
||||||
|
|
||||||
|
|
@ -15,12 +15,12 @@ import nuke
|
||||||
|
|
||||||
# removing logger handler created in avalon_core
|
# removing logger handler created in avalon_core
|
||||||
for name, handler in [(handler.get_name(), handler)
|
for name, handler in [(handler.get_name(), handler)
|
||||||
for handler in pype.Logger.logging.root.handlers[:]]:
|
for handler in api.Logger.logging.root.handlers[:]]:
|
||||||
if "pype" not in str(name).lower():
|
if "pype" not in str(name).lower():
|
||||||
pype.Logger.logging.root.removeHandler(handler)
|
api.Logger.logging.root.removeHandler(handler)
|
||||||
|
|
||||||
|
|
||||||
log = pype.Logger.getLogger(__name__, "nuke")
|
log = api.Logger.getLogger(__name__, "nuke")
|
||||||
|
|
||||||
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
AVALON_CONFIG = os.getenv("AVALON_CONFIG", "pype")
|
||||||
|
|
||||||
|
|
@ -37,7 +37,7 @@ self = sys.modules[__name__]
|
||||||
self.nLogger = None
|
self.nLogger = None
|
||||||
|
|
||||||
|
|
||||||
class NukeHandler(pype.Logger.logging.Handler):
|
class NukeHandler(api.Logger.logging.Handler):
|
||||||
'''
|
'''
|
||||||
Nuke Handler - emits logs into nuke's script editor.
|
Nuke Handler - emits logs into nuke's script editor.
|
||||||
warning will emit nuke.warning()
|
warning will emit nuke.warning()
|
||||||
|
|
@ -45,7 +45,7 @@ class NukeHandler(pype.Logger.logging.Handler):
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pype.Logger.logging.Handler.__init__(self)
|
api.Logger.logging.Handler.__init__(self)
|
||||||
self.set_name("Pype_Nuke_Handler")
|
self.set_name("Pype_Nuke_Handler")
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
|
|
@ -65,11 +65,11 @@ class NukeHandler(pype.Logger.logging.Handler):
|
||||||
nuke_handler = NukeHandler()
|
nuke_handler = NukeHandler()
|
||||||
if nuke_handler.get_name() \
|
if nuke_handler.get_name() \
|
||||||
not in [handler.get_name()
|
not in [handler.get_name()
|
||||||
for handler in pype.Logger.logging.root.handlers[:]]:
|
for handler in api.Logger.logging.root.handlers[:]]:
|
||||||
pype.Logger.logging.getLogger().addHandler(nuke_handler)
|
api.Logger.logging.getLogger().addHandler(nuke_handler)
|
||||||
|
|
||||||
if not self.nLogger:
|
if not self.nLogger:
|
||||||
self.nLogger = pype.Logger
|
self.nLogger = api.Logger
|
||||||
|
|
||||||
|
|
||||||
def reload_config():
|
def reload_config():
|
||||||
|
|
@ -86,8 +86,6 @@ def reload_config():
|
||||||
"app.api",
|
"app.api",
|
||||||
"{}.api".format(AVALON_CONFIG),
|
"{}.api".format(AVALON_CONFIG),
|
||||||
"{}.templates".format(AVALON_CONFIG),
|
"{}.templates".format(AVALON_CONFIG),
|
||||||
"{}.nuke".format(AVALON_CONFIG),
|
|
||||||
"{}.nuke.lib".format(AVALON_CONFIG),
|
|
||||||
"{}.nuke.templates".format(AVALON_CONFIG),
|
"{}.nuke.templates".format(AVALON_CONFIG),
|
||||||
"{}.nuke.menu".format(AVALON_CONFIG)
|
"{}.nuke.menu".format(AVALON_CONFIG)
|
||||||
):
|
):
|
||||||
|
|
@ -100,7 +98,8 @@ def reload_config():
|
||||||
|
|
||||||
|
|
||||||
def install():
|
def install():
|
||||||
pype.fill_avalon_workdir()
|
|
||||||
|
api.fill_avalon_workdir()
|
||||||
reload_config()
|
reload_config()
|
||||||
|
|
||||||
log.info("Registering Nuke plug-ins..")
|
log.info("Registering Nuke plug-ins..")
|
||||||
|
|
@ -128,7 +127,7 @@ def install():
|
||||||
menu.install()
|
menu.install()
|
||||||
|
|
||||||
# load data from templates
|
# load data from templates
|
||||||
pype.load_data_from_templates()
|
api.load_data_from_templates()
|
||||||
|
|
||||||
|
|
||||||
def uninstall():
|
def uninstall():
|
||||||
|
|
@ -140,7 +139,7 @@ def uninstall():
|
||||||
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
pyblish.deregister_callback("instanceToggled", on_pyblish_instance_toggled)
|
||||||
|
|
||||||
# reset data from templates
|
# reset data from templates
|
||||||
pype.reset_data_from_templates()
|
api.reset_data_from_templates()
|
||||||
|
|
||||||
|
|
||||||
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
def on_pyblish_instance_toggled(instance, old_value, new_value):
|
||||||
|
|
|
||||||
157
pype/nuke/lib.py
157
pype/nuke/lib.py
|
|
@ -2,6 +2,7 @@ import sys
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from pprint import pprint
|
from pprint import pprint
|
||||||
from avalon.vendor.Qt import QtGui
|
from avalon.vendor.Qt import QtGui
|
||||||
|
from avalon import api, io
|
||||||
import avalon.nuke
|
import avalon.nuke
|
||||||
import pype.api as pype
|
import pype.api as pype
|
||||||
import nuke
|
import nuke
|
||||||
|
|
@ -99,57 +100,6 @@ def add_rendering_knobs(node):
|
||||||
return node
|
return node
|
||||||
|
|
||||||
|
|
||||||
def update_frame_range(start, end, root=None):
|
|
||||||
"""Set Nuke script start and end frame range
|
|
||||||
|
|
||||||
Args:
|
|
||||||
start (float, int): start frame
|
|
||||||
end (float, int): end frame
|
|
||||||
root (object, Optional): root object from nuke's script
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
None
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
knobs = {
|
|
||||||
"first_frame": start,
|
|
||||||
"last_frame": end
|
|
||||||
}
|
|
||||||
|
|
||||||
with avalon.nuke.viewer_update_and_undo_stop():
|
|
||||||
for key, value in knobs.items():
|
|
||||||
if root:
|
|
||||||
root[key].setValue(value)
|
|
||||||
else:
|
|
||||||
nuke.root()[key].setValue(value)
|
|
||||||
|
|
||||||
|
|
||||||
def get_additional_data(container):
|
|
||||||
"""Get Nuke's related data for the container
|
|
||||||
|
|
||||||
Args:
|
|
||||||
container(dict): the container found by the ls() function
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict
|
|
||||||
"""
|
|
||||||
|
|
||||||
node = container["_tool"]
|
|
||||||
tile_color = node['tile_color'].value()
|
|
||||||
if tile_color is None:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
hex = '%08x' % tile_color
|
|
||||||
rgba = [
|
|
||||||
float(int(hex[0:2], 16)) / 255.0,
|
|
||||||
float(int(hex[2:4], 16)) / 255.0,
|
|
||||||
float(int(hex[4:6], 16)) / 255.0
|
|
||||||
]
|
|
||||||
|
|
||||||
return {"color": QtGui.QColor().fromRgbF(rgba[0], rgba[1], rgba[2])}
|
|
||||||
|
|
||||||
|
|
||||||
def set_viewers_colorspace(viewer):
|
def set_viewers_colorspace(viewer):
|
||||||
assert isinstance(viewer, dict), log.error(
|
assert isinstance(viewer, dict), log.error(
|
||||||
"set_viewers_colorspace(): argument should be dictionary")
|
"set_viewers_colorspace(): argument should be dictionary")
|
||||||
|
|
@ -245,6 +195,111 @@ def get_avalon_knob_data(node):
|
||||||
return None
|
return None
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
def reset_resolution():
|
||||||
|
"""Set resolution to project resolution."""
|
||||||
|
log.info("Reseting resolution")
|
||||||
|
project = io.find_one({"type": "project"})
|
||||||
|
asset = api.Session["AVALON_ASSET"]
|
||||||
|
asset = io.find_one({"name": asset, "type": "asset"})
|
||||||
|
|
||||||
|
try:
|
||||||
|
width = asset["data"].get("resolution_width", 1920)
|
||||||
|
height = asset["data"].get("resolution_height", 1080)
|
||||||
|
pixel_aspect = asset["data"].get("pixel_aspect", 1)
|
||||||
|
bbox = asset["data"].get("crop", "0.0.1920.1080")
|
||||||
|
|
||||||
|
try:
|
||||||
|
x, y, r, t = bbox.split(".")
|
||||||
|
except Exception as e:
|
||||||
|
x = 0
|
||||||
|
y = 0
|
||||||
|
r = width
|
||||||
|
t = height
|
||||||
|
log.error("{}: {} \nFormat:Crop need to be set with dots, example: "
|
||||||
|
"0.0.1920.1080, /nSetting to default".format(__name__, e))
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
log.warning(
|
||||||
|
"No resolution information found for \"{0}\".".format(
|
||||||
|
project["name"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
used_formats = list()
|
||||||
|
for f in nuke.formats():
|
||||||
|
if project["name"] in str(f.name()):
|
||||||
|
used_formats.append(f)
|
||||||
|
else:
|
||||||
|
format_name = project["name"] + "_1"
|
||||||
|
|
||||||
|
crnt_fmt_str = ""
|
||||||
|
if used_formats:
|
||||||
|
check_format = used_formats[-1]
|
||||||
|
format_name = "{}_{}".format(
|
||||||
|
project["name"],
|
||||||
|
int(used_formats[-1].name()[-1])+1
|
||||||
|
)
|
||||||
|
log.info(
|
||||||
|
"Format exists: {}. "
|
||||||
|
"Will create new: {}...".format(
|
||||||
|
used_formats[-1].name(),
|
||||||
|
format_name)
|
||||||
|
)
|
||||||
|
crnt_fmt_kargs = {
|
||||||
|
"width": (check_format.width()),
|
||||||
|
"height": (check_format.height()),
|
||||||
|
"x": int(check_format.x()),
|
||||||
|
"y": int(check_format.y()),
|
||||||
|
"r": int(check_format.r()),
|
||||||
|
"t": int(check_format.t()),
|
||||||
|
"pixel_aspect": float(check_format.pixelAspect())
|
||||||
|
}
|
||||||
|
crnt_fmt_str = make_format_string(**crnt_fmt_kargs)
|
||||||
|
log.info("crnt_fmt_str: {}".format(crnt_fmt_str))
|
||||||
|
|
||||||
|
new_fmt_kargs = {
|
||||||
|
"width": int(width),
|
||||||
|
"height": int(height),
|
||||||
|
"x": int(x),
|
||||||
|
"y": int(y),
|
||||||
|
"r": int(r),
|
||||||
|
"t": int(t),
|
||||||
|
"pixel_aspect": float(pixel_aspect),
|
||||||
|
"project_name": format_name
|
||||||
|
}
|
||||||
|
|
||||||
|
new_fmt_str = make_format_string(**new_fmt_kargs)
|
||||||
|
log.info("new_fmt_str: {}".format(new_fmt_str))
|
||||||
|
|
||||||
|
if new_fmt_str not in crnt_fmt_str:
|
||||||
|
make_format(frm_str=new_fmt_str,
|
||||||
|
project_name=new_fmt_kargs["project_name"])
|
||||||
|
|
||||||
|
log.info("Format is set")
|
||||||
|
|
||||||
|
|
||||||
|
def make_format_string(**args):
|
||||||
|
format_str = (
|
||||||
|
"{width} "
|
||||||
|
"{height} "
|
||||||
|
"{x} "
|
||||||
|
"{y} "
|
||||||
|
"{r} "
|
||||||
|
"{t} "
|
||||||
|
"{pixel_aspect:.2f}".format(**args)
|
||||||
|
)
|
||||||
|
return format_str
|
||||||
|
|
||||||
|
|
||||||
|
def make_format(**args):
|
||||||
|
log.info("Format does't exist, will create: \n{}".format(args))
|
||||||
|
nuke.addFormat("{frm_str} "
|
||||||
|
"{project_name}".format(**args))
|
||||||
|
nuke.root()["format"].setValue("{project_name}".format(**args))
|
||||||
|
|
||||||
|
|
||||||
# TODO: bellow functions are wip and needs to be check where they are used
|
# TODO: bellow functions are wip and needs to be check where they are used
|
||||||
# ------------------------------------
|
# ------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,17 @@ from pype.nuke import lib
|
||||||
|
|
||||||
|
|
||||||
def install():
|
def install():
|
||||||
|
|
||||||
menubar = nuke.menu("Nuke")
|
menubar = nuke.menu("Nuke")
|
||||||
menu = menubar.findItem(Session["AVALON_LABEL"])
|
menu = menubar.findItem(Session["AVALON_LABEL"])
|
||||||
|
|
||||||
menu.addSeparator()
|
# replace reset resolution from avalon core to pype's
|
||||||
menu.addCommand("Set colorspace...", lib.set_colorspace)
|
name = "Reset Resolution"
|
||||||
|
rm_item = [(i, item)
|
||||||
|
for i, item in enumerate(menu.items())
|
||||||
|
if name in item.name()][0]
|
||||||
|
menu.removeItem(rm_item[1].name())
|
||||||
|
menu.addCommand(rm_item[1].name(), lib.reset_resolution, index=rm_item[0])
|
||||||
|
|
||||||
|
# add colorspace menu item
|
||||||
|
menu.addCommand("Set colorspace...", lib.set_colorspace, index=rm_item[0]+1)
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
from .lib import *
|
|
||||||
|
|
||||||
|
|
||||||
def load_capture_preset(path):
|
|
||||||
import capture_gui
|
|
||||||
import capture
|
|
||||||
|
|
||||||
path = path
|
|
||||||
preset = capture_gui.lib.load_json(path)
|
|
||||||
print preset
|
|
||||||
|
|
||||||
options = dict()
|
|
||||||
|
|
||||||
# CODEC
|
|
||||||
id = 'Codec'
|
|
||||||
for key in preset[id]:
|
|
||||||
options[str(key)] = preset[id][key]
|
|
||||||
|
|
||||||
# GENERIC
|
|
||||||
id = 'Generic'
|
|
||||||
for key in preset[id]:
|
|
||||||
if key.startswith('isolate'):
|
|
||||||
pass
|
|
||||||
# options['isolate'] = preset[id][key]
|
|
||||||
else:
|
|
||||||
options[str(key)] = preset[id][key]
|
|
||||||
|
|
||||||
# RESOLUTION
|
|
||||||
id = 'Resolution'
|
|
||||||
options['height'] = preset[id]['height']
|
|
||||||
options['width'] = preset[id]['width']
|
|
||||||
|
|
||||||
# DISPLAY OPTIONS
|
|
||||||
id = 'Display Options'
|
|
||||||
disp_options = {}
|
|
||||||
for key in preset['Display Options']:
|
|
||||||
if key.startswith('background'):
|
|
||||||
disp_options[key] = preset['Display Options'][key]
|
|
||||||
else:
|
|
||||||
disp_options['displayGradient'] = True
|
|
||||||
|
|
||||||
options['display_options'] = disp_options
|
|
||||||
|
|
||||||
# VIEWPORT OPTIONS
|
|
||||||
temp_options = {}
|
|
||||||
id = 'Renderer'
|
|
||||||
for key in preset[id]:
|
|
||||||
temp_options[str(key)] = preset[id][key]
|
|
||||||
|
|
||||||
temp_options2 = {}
|
|
||||||
id = 'Viewport Options'
|
|
||||||
light_options = {0: "default",
|
|
||||||
1: 'all',
|
|
||||||
2: 'selected',
|
|
||||||
3: 'flat',
|
|
||||||
4: 'nolights'}
|
|
||||||
for key in preset[id]:
|
|
||||||
if key == 'high_quality':
|
|
||||||
temp_options2['multiSampleEnable'] = True
|
|
||||||
temp_options2['multiSampleCount'] = 4
|
|
||||||
temp_options2['textureMaxResolution'] = 512
|
|
||||||
temp_options2['enableTextureMaxRes'] = True
|
|
||||||
|
|
||||||
if key == 'alphaCut':
|
|
||||||
temp_options2['transparencyAlgorithm'] = 5
|
|
||||||
temp_options2['transparencyQuality'] = 1
|
|
||||||
|
|
||||||
if key == 'headsUpDisplay':
|
|
||||||
temp_options['headsUpDisplay'] = True
|
|
||||||
|
|
||||||
if key == 'displayLights':
|
|
||||||
temp_options[str(key)] = light_options[preset[id][key]]
|
|
||||||
else:
|
|
||||||
temp_options[str(key)] = preset[id][key]
|
|
||||||
|
|
||||||
for key in ['override_viewport_options', 'high_quality', 'alphaCut']:
|
|
||||||
temp_options.pop(key, None)
|
|
||||||
|
|
||||||
options['viewport_options'] = temp_options
|
|
||||||
options['viewport2_options'] = temp_options2
|
|
||||||
|
|
||||||
# use active sound track
|
|
||||||
scene = capture.parse_active_scene()
|
|
||||||
options['sound'] = scene['sound']
|
|
||||||
cam_options = dict()
|
|
||||||
cam_options['overscan'] = 1.0
|
|
||||||
cam_options['displayFieldChart'] = False
|
|
||||||
cam_options['displayFilmGate'] = False
|
|
||||||
cam_options['displayFilmOrigin'] = False
|
|
||||||
cam_options['displayFilmPivot'] = False
|
|
||||||
cam_options['displayGateMask'] = False
|
|
||||||
cam_options['displayResolution'] = False
|
|
||||||
cam_options['displaySafeAction'] = False
|
|
||||||
cam_options['displaySafeTitle'] = False
|
|
||||||
|
|
||||||
# options['display_options'] = temp_options
|
|
||||||
|
|
||||||
return options
|
|
||||||
|
|
@ -105,3 +105,99 @@ def filter_instances(context, plugin):
|
||||||
instances = pyblish.api.instances_by_plugin(allInstances, plugin)
|
instances = pyblish.api.instances_by_plugin(allInstances, plugin)
|
||||||
|
|
||||||
return instances
|
return instances
|
||||||
|
|
||||||
|
def load_capture_preset(path):
|
||||||
|
import capture_gui
|
||||||
|
import capture
|
||||||
|
|
||||||
|
path = path
|
||||||
|
preset = capture_gui.lib.load_json(path)
|
||||||
|
print preset
|
||||||
|
|
||||||
|
options = dict()
|
||||||
|
|
||||||
|
# CODEC
|
||||||
|
id = 'Codec'
|
||||||
|
for key in preset[id]:
|
||||||
|
options[str(key)] = preset[id][key]
|
||||||
|
|
||||||
|
# GENERIC
|
||||||
|
id = 'Generic'
|
||||||
|
for key in preset[id]:
|
||||||
|
if key.startswith('isolate'):
|
||||||
|
pass
|
||||||
|
# options['isolate'] = preset[id][key]
|
||||||
|
else:
|
||||||
|
options[str(key)] = preset[id][key]
|
||||||
|
|
||||||
|
# RESOLUTION
|
||||||
|
id = 'Resolution'
|
||||||
|
options['height'] = preset[id]['height']
|
||||||
|
options['width'] = preset[id]['width']
|
||||||
|
|
||||||
|
# DISPLAY OPTIONS
|
||||||
|
id = 'Display Options'
|
||||||
|
disp_options = {}
|
||||||
|
for key in preset['Display Options']:
|
||||||
|
if key.startswith('background'):
|
||||||
|
disp_options[key] = preset['Display Options'][key]
|
||||||
|
else:
|
||||||
|
disp_options['displayGradient'] = True
|
||||||
|
|
||||||
|
options['display_options'] = disp_options
|
||||||
|
|
||||||
|
# VIEWPORT OPTIONS
|
||||||
|
temp_options = {}
|
||||||
|
id = 'Renderer'
|
||||||
|
for key in preset[id]:
|
||||||
|
temp_options[str(key)] = preset[id][key]
|
||||||
|
|
||||||
|
temp_options2 = {}
|
||||||
|
id = 'Viewport Options'
|
||||||
|
light_options = {0: "default",
|
||||||
|
1: 'all',
|
||||||
|
2: 'selected',
|
||||||
|
3: 'flat',
|
||||||
|
4: 'nolights'}
|
||||||
|
for key in preset[id]:
|
||||||
|
if key == 'high_quality':
|
||||||
|
temp_options2['multiSampleEnable'] = True
|
||||||
|
temp_options2['multiSampleCount'] = 4
|
||||||
|
temp_options2['textureMaxResolution'] = 512
|
||||||
|
temp_options2['enableTextureMaxRes'] = True
|
||||||
|
|
||||||
|
if key == 'alphaCut':
|
||||||
|
temp_options2['transparencyAlgorithm'] = 5
|
||||||
|
temp_options2['transparencyQuality'] = 1
|
||||||
|
|
||||||
|
if key == 'headsUpDisplay':
|
||||||
|
temp_options['headsUpDisplay'] = True
|
||||||
|
|
||||||
|
if key == 'displayLights':
|
||||||
|
temp_options[str(key)] = light_options[preset[id][key]]
|
||||||
|
else:
|
||||||
|
temp_options[str(key)] = preset[id][key]
|
||||||
|
|
||||||
|
for key in ['override_viewport_options', 'high_quality', 'alphaCut']:
|
||||||
|
temp_options.pop(key, None)
|
||||||
|
|
||||||
|
options['viewport_options'] = temp_options
|
||||||
|
options['viewport2_options'] = temp_options2
|
||||||
|
|
||||||
|
# use active sound track
|
||||||
|
scene = capture.parse_active_scene()
|
||||||
|
options['sound'] = scene['sound']
|
||||||
|
cam_options = dict()
|
||||||
|
cam_options['overscan'] = 1.0
|
||||||
|
cam_options['displayFieldChart'] = False
|
||||||
|
cam_options['displayFilmGate'] = False
|
||||||
|
cam_options['displayFilmOrigin'] = False
|
||||||
|
cam_options['displayFilmPivot'] = False
|
||||||
|
cam_options['displayGateMask'] = False
|
||||||
|
cam_options['displayResolution'] = False
|
||||||
|
cam_options['displaySafeAction'] = False
|
||||||
|
cam_options['displaySafeTitle'] = False
|
||||||
|
|
||||||
|
# options['display_options'] = temp_options
|
||||||
|
|
||||||
|
return options
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue